• Jetzt anmelden. Es dauert nur 2 Minuten und ist kostenlos!

MYSQLI vs PDO

Werbung:
Werbung:
Es gibt nur einen Grund: um eine einheitliche API für die Behandlung von Datenbanken zu haben (mal abgesehen von Doctrine2, Propel, Zend_Db oder NoSQL).
 
Und genau deswegen benutze ich mysqli, weil ich als Datenbank nur MySQL benutze. Sollte ich ein Projekt machen, bei dem es wahrscheinlich ist, dass mehr als 2% der Benutzer nicht MySQL haben, würde ich meine Meinung vielleicht ändern.

Mit anderen Worten: es hängt vom Projekttypen ab. Sowohl mysql- mysqli- als auch pdo- könnten nicht auf einem Server installiert sein. mysqli ist auf allen meinen Hostern (ich rede von kostenpflichtigen) vorhanden und PDO ist auf vier von fünf vorhanden.
 
Es geht schneller als man denkt, dass man die Datenbank wechselt, bzw. auf unterschiedliche zugreift. Für 0 Mehraufwand (Lernaufwand ausgenommen) gibt's das dazu. mysql_* und MySQL gehören aus PHP entfernt.

Ein Hoster der kein PDO kann gehört gekündigt*.

* Wobei man ja auf Mysqli zurückfällt.
 
Zuletzt bearbeitet:
Werbung:
Es geht schneller als man denkt, dass man die Datenbank wechselt, bzw. auf unterschiedliche zugreift. Für 0 Mehraufwand (Lernaufwand ausgenommen) gibt's das dazu. mysql_* und MySQL gehören aus PHP entfernt.

Ein Hoster der kein PDO kann gehört gekündigt*.

* Wobei man ja auf Mysqli zurückfällt.
Das ist deine Meinung.
 
Die APIs von mysqli und PDO sind vom Funktionsumfang her quasi identisch. Ich persönlich finde die PDO-API logischer aufgebaut, da sie ohne das seltsame Referenzen-Geschiebe beim Parameter Binding auskommt (vgl. PHP: mysqli_stmt::bind_param - Manual) oder da es etwa heißt $pdo->beginTransaction(); und nicht $mysqli->autocommit(false);, aber das mag Geschmackssache sein.

Meiner Meinung nach sollte PDO (oder ein „echter“ ORM/DBMS-Abstraktionslayer, siehe crash) (Object Relational Mapper, Database Management System) immer die erste Wahl sein. Ich bin da selbst nicht sonderlich konsequent, aber eigentlich gibt es keine Entschuldigung, nicht DBMS-unabhängig zu programmieren.

Das einzige Argument gegen PDO scheint zu sein:

Asterixus schrieb:
Sowohl mysql- mysqli- als auch pdo- könnten nicht auf einem Server installiert sein. mysqli ist auf allen meinen Hostern (ich rede von kostenpflichtigen) vorhanden und PDO ist auf vier von fünf vorhanden.

Wobei ich da spontan auch empfehlen würde, den Webhoster zu wechseln. Zahlen zur Verbreitung wären aber durchaus interessant.

Noch mal zur Klärung:

PDO provides a data-access abstraction layer, which means that, regardless of which database you're using, you use the same functions to issue queries and fetch data. PDO does not provide a database abstraction; it doesn't rewrite SQL or emulate missing features. You should use a full-blown abstraction layer if you need that facility.

- PHP: Introduction - Manual

Einige „full-blown abstraction layer“ hat wie gesagt crash aufgelistet. PDO ist wie ebenfalls gesagt lediglich eine Abstraktion der Zugriffs-API, nicht der jeweils nötigen SQL-Query-Syntax selbst.

Was aus PHP entfernt werden sollte (aber wohl auf absehbare Zeit nicht entfernt werden wird, um nicht 99 % aller Anwendungen zu zerstören), ist der alte mysql-Adapter. mysqli könnte einerseits meinetwegen auch entfernt werden, aber eher aus dem Grund, weil ich keinen Sinn darin sehe, es zu nutzen, weil es eben PDO gibt. Andererseits sehe ich kein Problem darin, „low-level“-Zugriffsbefehle beizubehalten. Es sollte nur stärker kommuniziert werden, dass es Zugriffsmethoden wie PDO gibt.

Ich würde gern zu Gunsten von PDO stärker von mysqli abraten, da ich die Vorteile auf Seiten von PDO sehe und da ich der Ansicht bin, dass sich die Leute in den allermeisten Fällen keinen Gefallen damit tun, auf mysqli zu setzen. Die Einstellung ist aber leider eben subjektiv gefärbt. Ich schreibe deshalb meist: „Nutze statt mysql besser mysqli oder (meiner Ansicht nach noch besser) PDO oder auf lange Sicht am besten gleich eine echte Abstraktionsschicht (ORM).“ Das Problem dabei ist, dass die Leute oft schon genug Probleme mit der alten mysql-Extension haben und PDO oder vor allem ORM eben noch weitere Anwendungskonzepte verkörpern, die erst gelernt werden müssen. Das ist oft einfach zu viel Holz auf einmal. mysql reicht zudem in vielen Fällen, auch wenn es die nachteiligste aller Lösungen ist. Sowas ist leider schwer kommunizierbar.

Gerade bei Datenbankzugriff führt fehlende „Aufklärung“ zudem sehr oft dazu, dass Leute anfangen, sich ihre eigenen kleinen Wrapper/Abstraktionsschichten zu schreiben, obwohl diese schon komplett vorliegen (etwa Zend_Db, Doctrine). Ich halte es aus diversen Gründen für absolut unvorteilhaft, an dieser Stelle das Rad neu zu erfinden.

Ähnlich denke ich zum Beispiel über SimpleXML im Verhältnis zu DOM. Ich möchte von ersterem abraten, sehe aber keine eindeutig objektive Veranlassung dazu. Subjektiv gefärbte Meinungen gehen dagegen oft in Richtung Trolling.
 
Zuletzt bearbeitet:
PDO gibt's nun seit PHP 5.1, da gehört mysql_* kurzfristig missbilligt und langfristig entfernt, wobei man Mysqli auf mysql_ mappen könnte; statt einer Resource-ID wird eine Objekt-Instanz durch gereicht.

Es gibt einen guten Grund SimpleXML nicht zu nutzen: DOM ist viel mächtiger, selbst einfache Sachen lassen sich mit SimpleXML nicht erledigen. Immer wenn ich SimpleXML verwendet habe, kam ich schnell an dessen Grenzen. Und DOM zu verstehen und zu können hilft einen, wie Regex, in anderen Sprachen sehr weiter.
 
Werbung:
attachment.php
Edit
attachment.php


Startpunkt in der Doctrine-Dokumentation:

- Getting Started — Doctrine 2 ORM v2.0.0 documentation

Es wäre sinnvoll, dort parallel zu lesen.


* * *​

Kleines Doctrine-Beispiel:

1. Doctrine 2.0.0 runterladen: Doctrine - Download Object Relational Mapper 2.0.0

2. Entpacken und das Doctrine-Verzeichnis aus dem Archiv in das library-Verzeichnis des Projekts kopieren.

3. Das Symfony-Verzeichnis aus dem library/Doctrine-Verzeichnis heraus ins library-Verzeichnis verschieben.

4. Übrige Verzeichnisse und Dateien anlegen (siehe unten). ./library/org/example/Guestbook/Proxies und ./data müssen vom Webserver beschrieben werden können.

Code:
.
├── config
│   └── mappings
│       └── org.example.Guestbook.Entities.Entry.dcm.yml
├── data
├── index.php
└── library
    ├── Doctrine […]
    ├── org
    │   └── example
    │       └── Guestbook
    │           ├── Entities
    │           │   └── Entry.php
    │           └── Proxies
    └── Symfony
        └── Component
            ├── Console […]
            └── Yaml […]

5. Inhalt von ./library/org/example/Guestbook/Entities/Entry.php:

Code:
<?php

namespace org\example\Guestbook\Entities;

class Entry
{
    protected $id;
    protected $author;
    protected $createdOn;
    protected $homepage;
    protected $content;

    public function getId()
    {
        return $this->id;
    }

    public function setId($id)
    {
        $this->id = $id;
    }

    public function getAuthor()
    {
        return $this->author;
    }

    public function setAuthor($author)
    {
        $this->author = $author;
    }

    public function getCreatedOn()
    {
        return $this->createdOn;
    }

    public function setCreatedOn($createdOn)
    {
        $this->createdOn = $createdOn;
    }

    public function getHomepage()
    {
        return $this->homepage;
    }

    public function setHomepage($homepage)
    {
        $this->homepage = $homepage;
    }

    public function getContent()
    {
        return $this->content;
    }

    public function setContent($content)
    {
        $this->content = $content;
    }
}

6. Inhalt von ./config/mappings/org.example.Guestbook.Entities.Entry.dcm.yml:

Code:
org\example\Guestbook\Entities\Entry:
  type: entity
  table: entries
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    author:
      type: string
    createdOn:
      type: datetime
    homepage:
      type: string
    content:
      type: text

7. Inhalt von ./index.php:

Code:
<?php

use Doctrine\Common\Cache\ArrayCache,
    Doctrine\Common\EventManager,
    Doctrine\ORM\Configuration,
    Doctrine\ORM\EntityManager,
    Doctrine\ORM\Mapping\ClassMetadataFactory,
    Doctrine\ORM\Mapping\Driver\YamlDriver,
    Doctrine\ORM\Tools\SchemaTool,

    org\example\Guestbook\Entities\Entry;

/**
 *
 * @param array $dbConnectionParams
 * @return EntityManager
 */
function initializeOrm($dbConnectionParams)
{
    $config = new Configuration();

    // Proxy Configuration
    $config->setProxyDir('./library/org/example/Guestbook/Proxies');
    $config->setProxyNamespace('org\example\Guestbook\Proxies');

    $config->setAutoGenerateProxyClasses(true);

    // Entity alias configuration
    $config->addEntityNamespace('Gb', 'org\\example\\Guestbook\\Entities');

    // Mapping Configuration
    $config->setMetadataDriverImpl(new YamlDriver('./config/mappings'));

    // Caching Configuration
    $cache = new ArrayCache();
    $config->setMetadataCacheImpl($cache);
    $config->setQueryCacheImpl($cache);

    $entityManager = EntityManager::create($dbConnectionParams, $config);

    return $entityManager;
}

/**
 *
 * @param EntityManager $entityManager
 */
function createSchema(EntityManager $entityManager)
{
    $st = new SchemaTool($entityManager);
    $st->createSchema($entityManager->getMetadataFactory()->getAllMetadata());
}

/**
 *
 * @param EntityManager $entityManager
 * @param int $from
 * @param int $maxAmount
 * @return array
 */
function getEntries(EntityManager $entityManager, $from = 0, $maxAmount = 20)
{
    $dql = "SELECT e FROM Gb:Entry e ORDER BY e.createdOn DESC";

    $query = $entityManager->createQuery($dql);
    $query->setFirstResult($from);
    $query->setMaxResults($maxAmount);

    return $query->getResult();
}

/**
 *
 * @param EntityManager $entityManager
 * @param Entry $entry
 */
function addEntry(EntityManager $entityManager, Entry $entry)
{
    $entityManager->persist($entry);
    $entityManager->flush();
}

/**
 *
 * @param string $s
 * @return string
 */
function escape($s)
{
    return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}

error_reporting(-1);

set_include_path(realpath('./library') . PATH_SEPARATOR . get_include_path());

spl_autoload_register(function ($className)
{
    require_once str_replace('\\', '/', $className) . '.php';
});

/* SQLite config */
$conn = array(
    'driver' => 'pdo_sqlite',
    'path'   => './data/db.sqlite'
);

/* MySQL config */
//$conn = array(
//    'driver'   => 'pdo_mysql',
//    'user'     => '',
//    'password' => '',
//    'host'     => 'localhost',
//    'dbname'   => 'test'
//);

/* PostgreSQL config */
//$conn = array(
//    'driver'   => 'pdo_pgsql',
//    'user'     => '',
//    'password' => '',
//    'host'     => 'localhost',
//    'dbname'   => 'test'
//);

$entityManager = initializeOrm($conn);

//createSchema($entityManager);

$errors = array();

if (count($_POST) > 0) {
    $vars['name'] =
        (isset($_POST['name'])) ? trim((string) $_POST['name']) : '';
    $vars['homepage'] =
        (isset($_POST['homepage'])) ? trim((string) $_POST['homepage']) : '';
    $vars['content'] =
        (isset($_POST['content'])) ? trim((string) $_POST['content']) : '';

    if ($vars['name'] === '' || $vars['content'] === '') {
        $errors[] = 'Please enter your name and a text.';
    }

    if ($vars['homepage'] !== ''
            && !filter_var($vars['homepage'], FILTER_VALIDATE_URL)
    ) {
        $errors[] = 'Please specify a valid URL for homepage or leave the field
                empty.';
    }

    if (count($errors) === 0) {
        $newEntry = new Entry();
        $newEntry->setHomepage($vars['homepage']);
        $newEntry->setAuthor($vars['name']);
        $newEntry->setCreatedOn(new DateTime(null, new DateTimeZone('UTC')));
        $newEntry->setContent($vars['content']);

        addEntry($entityManager, $newEntry);

        $_POST = array();
    }
}

$tpl = array();
$tpl['errors']  = $errors;
$tpl['entries'] = getEntries($entityManager, 0, 50);

?><!DOCTYPE html>

<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Doctrine/Guestbook demo</title>
    <style type="text/css">

* {
    padding: 0;
    margin: 0;
}

body {
    padding: 2em;
}

.entry {
    border: 0.0625em solid #ccc;
    padding: 1em;
    margin-bottom: 1em;
}

    </style>
</head>

<body>

    <h1>Guestbook</h1>

    <?php if (count($tpl['errors']) > 0) : ?>
        <p>One or more errors occurred.</p>
        <?php foreach ($tpl['errors'] as $error) : ?>
            <p><?php echo escape($error); ?></p>
        <?php endforeach; ?>
    <?php endif; ?>

    <h2>Add entry</h2>

    <form method="post" action="">
        <p>Name: <input name="name" type="text" value="<?php
            echo (isset($_POST['name']))
                ? escape((string) $_POST['name']) : '';
            ?>" /></p>

        <p>Homepage: <input name="homepage" type="text" value="<?php
            echo (isset($_POST['homepage']))
                    ? escape((string) $_POST['homepage']) : '';
            ?>" /></p>

        <p>Content:</p>
        <p><textarea name="content" cols="40" rows="15"><?php
            echo (isset($_POST['content']))
                ? escape((string) $_POST['content']) : '';
            ?></textarea></p>

        <p><input type="submit" value="Send" /></p>
    </form>

    <hr />

    <h2>Entries</h2>

    <?php if (count($tpl['entries']) === 0) : ?>

        <p>No entries yet.</p>

    <?php else : ?>

        <?php foreach ($tpl['entries'] as $entry) : ?>
        <div class="entry">
            <p>#<?php echo $entry->getid(); ?>
                <?php echo escape($entry->getAuthor()); ?>:</p>
            <p><?php echo nl2br(escape($entry->getContent())); ?></p>
        </div>
        <?php endforeach; ?>

    <?php endif; ?>

</body>

</html>

8. Für jeden eingesetzten DBMS-Driver muss einmalig die createSchema-Funktion (derzeit auskommentiert) aufgerufen werden, um die entsprechende Tabelle zu erstellen.

Doctrine-Dokumentation: Welcome to Doctrine 2 ORM?s documentation! &mdash; Doctrine 2 ORM v2.0.0 documentation
 
Zuletzt bearbeitet:
Schönes ausführliches Beispiel. Erklärt wie's funktioniert, aber ich glaube die meisten werden von solcher Mächtigkeit abgeschreckt sein.

btw. spl_autoload_register(); reicht aus, wenn die Namespaces der Ordnerstruktur entsprechen.
 
Ja, ich denke, die Schwierigkeit sind die zahlreichen Konzepte, die auf einmal gelernt werden oder bereits bekannt sein müssen. Namespaces, ein gerüttelt Maß OOP, Entities und zugehöriges dateiübergreifendes Mapping, Mapping-Driver-Syntax (sei es Yaml, XML oder Annotations) und „Meta“-Datentypen, DQL (das ist *nicht* SQL), generell relationale Datenbankkonzepte, … Das ist nicht wenig. Die Doku von Doctrine ist deshalb leider auch nicht die einfachste, weil eben OOP und die relationalen Datenbankkonzepte vorausgesetzt werden.

Ich habe gestern überlegt, ob ich noch weitere Erklärungen hinzufügen soll, mich dann aber dagegen entschieden, weil ich nicht wüsste, wo man anfangen sollte. Mir erschien es aber sinnvoll, zumindest zu zeigen, wie Doctrine in einem Real-World-Beispiel überhaupt mal zum Laufen gebracht werden kann. Normalerweise werfen wir ja nur den Namen in den Raum, was die Einstiegshürde nicht eben verringert.

Vielleicht doch ein, zwei Sätze zu meinem Beispiel:

Der meiste Code hat mit Doctrine eigentlich nichts zu tun, sondern ist ganz normale PHP-Formularverarbeitung für ein Gästebuch.

$entityManager = initializeOrm($conn); ist das, was normalerweise die Verbindungsfunktion des Datenbank-Adapters ist, $entityManager ist gewissermaßen das, was sonst die Instanz der Datenbankklasse ist. Die Funktionen getEntries und addEntry könnten ebenfalls für jeden anderen DB-Adapter so geschrieben werden.

Was sicher abschrecken könnte, ist ein Dateinamen-Monster wie org.example.Guestbook.Entities.Entry.dcm.yml. Die Bezeichnung ist ein wenig meinem Ordnungswahn geschuldet. Ich bin ein Fan von „an URLs gebundene“ Namespaces (wie in Java verbreitet). Jeder kann „seinen“ Namespace zum Beispiel „Foo“ nennen, was dann trotz allem noch immer zu Benennungskonflikten führen kann. Aber einen Namespace „org\example“ zu nennen, ergibt im Grunde nur dann Sinn, wenn man der Inhaber dieser Domain ist. (Bin ich zwar nicht, aber ist ja auch bloß ein Beispiel.) Wer es nicht so genau nimmt, kann dort definitiv einige Verschachtelungsebenen einsparen. Doctrine selbst erwartet dann, dass diese Yaml-„Entity-Mapping“-Datei dem voll qualifizierten Klassennamen der entsprechenden PHP-Entity-Klasse entspricht, welcher in diesem Fall nunmal org\example\Guestbook\Entities\Entry lautet. Foo\Entry oder auch nur Entry wäre bei entsprechend abweichender Namespace-Nutzung völlig äquivalent, was Doctrine betrifft.

Wie die einzelnen Dateien zusammengehören, wird übrigens hier recht gut erklärt:

- Getting Started &mdash; Doctrine 2 ORM v2.0.0 documentation

Das dort behandelte Beispielprojekt (Bugtracker) ist allerdings eine Ecke komplizierter als mein Gästebuch mit nur einer Tabelle. Als Einstiegspunkt ist dieses Tutorial aber genau richtig.

btw. spl_autoload_register(); reicht aus, wenn die Namespaces der Ordnerstruktur entsprechen.

Das wäre schön, aber wenn ich richtig gelesen/getestet habe, scheint der Standard-SPL-Autoloader alle Klassennamen und somit Pfade in Kleinbuchstaben umzuwandeln. Das funktioniert dann zumindest nicht unter Linux. Oder übersehe ich was?
 
Zuletzt bearbeitet:
Werbung:
Zurück
Oben