sql >> Databáze >  >> RDS >> Mysql

Singleton alternativa pro PHP PDO

Použití singleton-pattern (nebo antipattern) je považováno za špatnou praxi, protože to velmi ztěžuje testování vašeho kódu a závislosti jsou velmi spletité, dokud se projekt v určitém okamžiku stane obtížně ovladatelným. Pro každý php-proces můžete mít pouze jednu pevnou instanci vašeho objektu. Při psaní automatizovaných unit-testů pro váš kód musíte být schopni nahradit objekt, který kód, který chcete testovat, používá, testovacím dvojitým, který se chová předvídatelným způsobem. Když kód, který chcete testovat, používá singleton, nemůžete ho nahradit testovacím dvojitým.

Nejlepší způsob (podle mých znalostí), jak uspořádat interakci mezi objekty (jako je váš objekt Database-Object a další objekty využívající databázi), by bylo obrátit směr závislostí. To znamená, že váš kód nepožaduje objekt, který potřebuje, z externího zdroje (ve většině případů globálního, jako je statická metoda „get_instance“ z vašeho kódu), ale místo toho získává svůj objekt závislosti (ten, který potřebuje) obsluhovaný zvenčí. než to potřebuje. Normálně byste použili Depency-Injection Manager/Container jako toto jeden z projektu symfony skládat své objekty.

Objekty, které používají objekt databáze, by jej dostaly při konstrukci. Může být injektován buď metodou setter nebo v konstruktoru. Ve většině případů (ne ve všech) je lepší vložit závislost (váš db-object) do konstruktoru, protože tak objekt, který používá db-object, nikdy nebude v neplatném stavu.

Příklad:

interface DatabaseInterface
{
    function query($statement, array $parameters = array());
}

interface UserLoaderInterface
{
    public function loadUser($userId);
}

class DB extends PDO implements DatabaseInterface
{
    function __construct(
        $dsn = 'mysql:host=localhost;dbname=kida',
        $username = 'root',
        $password = 'root',
    ) {
        try {
            parent::__construct($dsn, $username, $password, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'");
            parent::setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        } catch(PDOException $e) {
            echo $e->getMessage();
        }
    }

    function query($statement, array $parameters = array())
    {
        # ...
    }
}

class SomeFileBasedDB implements DatabaseInterface
{
    function __construct($filepath)
    {
        # ...
    }

    function query($statement, array $parameters = array())
    {
        # ...
    }
}

class UserLoader implements UserLoaderInterface
{
    protected $db;

    public function __construct(DatabaseInterface $db)
    {
        $this->db = $db;
    }

    public function loadUser($userId)
    {
        $row = $this->db->query("SELECT name, email FROM users WHERE id=?", [$userId]);

        $user = new User();
        $user->setName($row[0]);
        $user->setEmail($row[1]);

        return $user;
    }
}

# the following would be replaced by whatever DI software you use,
# but a simple array can show the concept.


# load this from a config file
$parameters = array();
$parameters['dsn'] = "mysql:host=my_db_server.com;dbname=kida_production";
$parameters['db_user'] = "mydbuser";
$parameters['db_pass'] = "mydbpassword";
$parameters['file_db_path'] = "/some/path/to/file.db";


# this will be set up in a seperate file to define how the objects are composed
# (in symfony, these are called 'services' and this would be defined in a 'services.xml' file)
$container = array();
$container['db'] = new DB($parameters['dsn'], $parameters['db_user'], $parameters['db_pass']);
$container['fileDb'] = new SomeFileBasedDB($parameters['file_db_path']);

# the same class (UserLoader) can now load it's users from different sources without having to know about it.
$container['userLoader'] = new UserLoader($container['db']);
# or: $container['userLoader'] = new UserLoader($container['fileDb']);

# you can easily change the behaviour of your objects by wrapping them into proxy objects.
# (In symfony this is called 'decorator-pattern')
$container['userLoader'] = new SomeUserLoaderProxy($container['userLoader'], $container['db']);

# here you can choose which user-loader is used by the user-controller
$container['userController'] = new UserController($container['fileUserLoader'], $container['viewRenderer']);

Všimněte si, jak o sobě různé třídy nevědí. Nejsou mezi nimi žádné přímé závislosti. Toho se dosáhne tím, že v konstruktoru není vyžadována skutečná třída, ale místo toho vyžaduje rozhraní, které poskytuje metody, které potřebuje.

Tímto způsobem můžete vždy napsat náhrady pro své třídy a jednoduše je nahradit v kontejneru depency-injection. Nemusíte kontrolovat celou kódovou základnu, protože náhrada musí pouze implementovat stejné rozhraní, které používají všechny ostatní třídy. Víte, že vše bude i nadále fungovat, protože každá komponenta používající starou třídu ví pouze o rozhraní a volá pouze metody, které rozhraní zná.

P.S.:Omluvte prosím mé neustálé narážky na projekt symfony, na to jsem právě zvyklý. Další projekty jako Drupal, Propel nebo Zend pravděpodobně mají podobné koncepty.




  1. Záleží na pořadí tabulek, na které se odkazuje v klauzuli ON v JOIN?

  2. Průběh celkem... s obratem

  3. Getting OperationalError:FATAL:Omlouváme se, příliš mnoho klientů již používá psycopg2

  4. MySQL Relational Division