Mysql
 sql >> база данни >  >> RDS >> Mysql

Singleton алтернатива за PHP PDO

Използването на singleton-pattern (или antipattern) се счита за лоша практика, защото прави тестването на вашия код много трудно и зависимостите са много сложни, докато проектът стане труден за управление в даден момент. Можете да имате само едно фиксирано копие на вашия обект на php-процес. Когато пишете автоматизирани модулни тестове за вашия код, трябва да можете да замените обекта, който кодът, който искате да тествате, използва с тест-двойник, който се държи по предвидим начин. Когато кодът, който искате да тествате, използва сингълтон, тогава не можете да го замените с тестов двоен.

Най-добрият начин (доколкото ми е известно) да организирате взаимодействието между обекти (като вашата база данни-обект и други обекти, използващи базата данни) би бил да обърнете посоката на зависимостите. Това означава, че вашият код не изисква обекта, от който се нуждае, от външен източник (в повечето случаи глобален, като статичния метод 'get_instance' от вашия код), а вместо това получава своя depency-object (този, от който се нуждае), обслужван отвън преди да има нужда от него. Обикновено бихте използвали Depency-Injection Manager/Container като това един от проекта symfony за съставяне на вашите обекти.

Обектите, които използват обекта на базата данни, ще го инжектират при конструирането. Може да се инжектира или чрез метод на сетер, или в конструктора. В повечето случаи (не във всички) е по-добре да инжектирате зависимостта (вашият db-обект) в конструктора, защото по този начин обектът, който използва db-обекта, никога няма да бъде в невалидно състояние.

Пример:

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']);

Забележете как различните класове не знаят един за друг. Между тях няма пряка зависимост. Това се прави, като не се изисква действителният клас в конструктора, а вместо това се изисква интерфейсът, който предоставя методите, от които се нуждае.

По този начин винаги можете да пишете заместители за вашите класове и просто да ги замените в контейнера за инжектиране на зависимости. Не е нужно да проверявате цялата кодова база, защото замяната просто трябва да имплементира същия интерфейс, който се използва от всички други класове. Знаете, че всичко ще продължи да работи, защото всеки компонент, използващ стария клас, знае само за интерфейса и извиква само методи, известни от интерфейса.

P.S.:Моля, извинете за постоянните ми препратки към проекта symfony, точно с него съм свикнал най-много. Други проекти като Drupal, Propel или Zend вероятно също имат концепции като тази.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как да извлечете следващия елемент в списък с MySQL записи?

  2. MySQL:Вмъкване на няколко реда с една и съща AI стойност

  3. Как да избегнете символи за боклук/боклук, докато четете данни от множество езици?

  4. Параметър MyBatis от HashMap

  5. Нов драйвер за MySQL причинява java.sql.SQLNonTransientConnectionException:изисква се CLIENT_PLUGIN_AUTH