Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Alternatywa singletona dla PHP PDO

Używanie wzorca singleton (lub antywzorca) jest uważane za złą praktykę, ponieważ sprawia, że ​​testowanie kodu jest bardzo trudne, a zależności bardzo zawiłe, dopóki projekt nie stanie się trudny do zarządzania w pewnym momencie. Możesz mieć tylko jedną stałą instancję swojego obiektu na proces php. Podczas pisania automatycznych testów jednostkowych dla swojego kodu musisz mieć możliwość zastąpienia obiektu, którego używa kod, który chcesz przetestować, na test-double, który zachowuje się w przewidywalny sposób. Gdy kod, który chcesz przetestować, używa singletona, nie możesz go zastąpić testowym double.

Najlepszym sposobem (zgodnie z moją wiedzą) na zorganizowanie interakcji między obiektami (takimi jak obiekt bazy danych i innymi obiektami korzystającymi z bazy danych) byłoby odwrócenie kierunku zależności. Oznacza to, że twój kod nie żąda obiektu, którego potrzebuje z zewnętrznego źródła (w większości przypadków globalnego, takiego jak statyczna metoda „get_instance” z twojego kodu), ale zamiast tego otrzymuje swój obiekt zależności (ten, którego potrzebuje) obsługiwany z zewnątrz zanim tego potrzebuje. Zwykle używałbyś Depency-Injection Manager/Container, takiego jak to jeden z projektu symfony komponować obiekty.

Obiekty, które używają obiektu bazy danych, zostaną wstrzyknięte podczas budowy. Może być wstrzykiwany za pomocą metody ustawiającej lub w konstruktorze. W większości przypadków (nie we wszystkich) lepiej jest wstrzyknąć zależność (twój obiekt-db) do konstruktora, ponieważ w ten sposób obiekt, który używa obiektu-db, nigdy nie będzie w nieprawidłowym stanie.

Przykład:

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

Zwróć uwagę, jak różne klasy nie wiedzą o sobie. Nie ma między nimi bezpośrednich zależności. Odbywa się to poprzez nie wymaganie rzeczywistej klasy w konstruktorze, ale wymaganie interfejsu, który zapewnia potrzebne metody.

W ten sposób zawsze możesz napisać zamienniki dla swoich klas i po prostu zastąpić je w kontenerze depency-injection. Nie musisz sprawdzać całej bazy kodu, ponieważ zamiennik musi tylko zaimplementować ten sam interfejs, który jest używany przez wszystkie inne klasy. Wiesz, że wszystko będzie nadal działać, ponieważ każdy komponent używający starej klasy wie tylko o interfejsie i wywołuje tylko metody znane z interfejsu.

P.S.:Proszę wybaczyć moje ciągłe odniesienia do projektu symfony, to jest właśnie to, do czego jestem najbardziej przyzwyczajony. Inne projekty, takie jak Drupal, Propel czy Zend, prawdopodobnie również mają takie koncepcje.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Podany klucz był za długi; maksymalna długość klucza to 767 bajtów — ASPNet Identity MySQL

  2. MYSQL:Pobieranie istniejącego klucza podstawowego podczas wstawiania rekordu ze zduplikowanym unikalnym kluczem?

  3. Jak monitorować bazy danych MySQL/MariaDB za pomocą Netdata w CentOS 7?

  4. Czy konieczne jest użycie mysql_real_escape_string(), gdy magic_quotes_gpc jest włączone?

  5. Zapisywanie ścieżki pliku przesłanego obrazu do bazy danych MySQL