Работа с базами даннных в Zend Expressive 3
На моей памяти, чаще всего для работы с базами данных в PHP-проектах используют Doctrine ORM. В версии 2 данная ORM использует паттерн Data Mapper.
Пока что обойдемся стандартным компонентом Zend\Db . Этот модуль использует паттерн Table Gateway. Для небольших объемов данных этот модуль вполне себе сгодится.
NB!
Про паттерны проектирования (Design Pattern) будет отдельный рассказ.
И так, устанавливаем модуль Zend\Db:
composer require zendframework/zend-db
Далее, предлагаю создать отдельный модуль, в котором сгруппируем код для работы с БД.
Делается это так: в консоле заходим в каталог проекта и выполняем команду для создания нового модуля:
module:create [-c|--composer COMPOSER] [-p|--modules-path MODULES-PATH] [--] <module>
$ composer expressive module:create Database
> expressive --ansi 'module:create' 'Database'
Using project autoloader based on current working directory
Created module Database in /var/www/domain.tld/src/Database
Generating autoload files
Registered autoloading rules and added configuration entry for module Database
На выходе получаем некоторую структуру каталогов и файлов нового модуля. Также, этот модуль прописывается в файлах composer.json и config.php.
Теперь нужно вписать настройки доступа к БД. Настройки пропишем в файле ConfigProvider.php:
public function __invoke() : array
{
return [
'dependencies' => $this->getDependencies(),
'templates' => $this->getTemplates(),
'db' => [
'driver' => 'Pdo_Pgsql',
'database' => 'derbysoft',
'username' => 'homestead',
'password' => 'secret',
'hostname' => 'localhost',
'port' => 5432,
],
];
}
Этот код добавляет секцию ‘db’ в конфигурацию приложения. Далее, рассказываем приложению, как же мы будем работать с БД.
public function getDependencies() : array
{
return [
'invokables' => [
],
'factories' => [
// эта фабрика нужна, чтобы из конфига достать настройки БД, а потом создать адаптер на основе этих настроек
\Zend\Db\Adapter\Adapter::class => \Zend\Db\Adapter\AdapterServiceFactory::class,
// фабрика, которая подготавливает объект для работы с таблицей customer
// в данном случае фабрика - это анонимная функция
Table\CustomerTable::class => function($container) {
// создаем соединение с БД
$dbAdapter = $container->get(AdapterInterface::class);
// говорим, какого типа результат ожидаем
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Model\Customer());
// говорим, что для данной таблицы будем использовать созданное выше соединение с БД и ожидаем в конце такой-то результат
return new Table\CustomerTable('customer', $dbAdapter, null, $resultSetPrototype);
},
],
];
}
Первое, что нужно - это некий класс Adapter, который указывает какая БД на каком сервере будет использоваться. В конструктор Adapter-а можно передать настройки самостоятельно, или можно использовать фабрику (Factory), которая будет брать настройки из конфигурации (которые добавили выше) для создания объекта Adapter.
Именно эту фабрику мы и объявляем, т.е. при запросе из DI-контейнера класса \Zend\Db\Adapter\Adapter будут исполняться методы класса \Zend\Db\Adapter\AdapterServiceFactory, которые и подготовят нужный нам объект.
Второе - создаем объект для операций с таблицей. Тут фабрика представляет собой анонимную функцию, в которой
- используется объявленный раннее Adapter (читай - подключение к БД),
- указывается, что каждую строку таблицы описывает какой-то класс
Т.е. описанное выше указывает, что при запросе из контейнера класса \Database\Table\CustomerTable будет
- создаваться подключение к БД с настройками из секции ‘db’ конфигурации приложения
- указываться, что при запросе будет возвращаться список объектов
\Database\Model\Customer
Теперь, нужно создать эти 2 класса:
- класс для создания объекта, в котором будут хранится данные для каждой строки таблицы
- класс, в котором будут описаны операции с таблицей
Создаем 2 файла в только что созданном модуле:
-
\Database\src\Model\Customer.php
Результатом запроса может быть объект или массив (это описано в классе ResultSet). Если целью является использовать данные как объект - то в качестве возвращаемого значения должен быть экземпляр класса ArrayObject.
<?php namespace Database\Model; class Customer extends \ArrayObject { public $id; public $username; public $password; public function exchangeArray(array $data) { $this->id = !empty($data['id']) ? $data['id'] : null; $this->username = !empty($data['username']) ? $data['username'] : null; $this->password = !empty($data['password']) ? $data['password'] : null; } }Класс ArrayObject позволяет работать с объектом, как с массивом, в том числе использовать соответствующие нотации. Например:
$object["property"]Но такие возможности накладывают определенные ограничения на сам объект. Одно из них - свойства объекта должны быть публичными.
-
\Database\src\Table\CustomerTable.php
2 класса - TableGateway и AbstractTableGateway объявляют некоторое количество полей и методов для работы с таблицей.
Типа, SELECT | INSERT | DELETE | …
Для использования этого функционала в своих классах, которые работают с таблицами, делается наследование от TableGateway.
Отличие классов TableGateway и AbstractTableGateway в том, что в классе TableGateway описан конструктор.
<?php namespace Database\Table; use Zend\Db\TableGateway\TableGateway; use Zend\Db\Adapter\AdapterInterface; use Zend\Db\ResultSet\ResultSet; use Zend\Db\ResultSet\ResultSetInterface; use Zend\Db\Sql\Sql; use Zend\Db\Sql\TableIdentifier; class CustomerTable extends TableGateway { /** * Constructor. * * @param $table * @param AdapterInterface $adapter * @param null $features * @param ResultSetInterface|null $resultSetPrototype * @param Sql|null $sql */ public function __construct( $table, AdapterInterface $adapter, $features = null, ResultSetInterface $resultSetPrototype = null, Sql $sql = null ) { parent::__construct($table, $adapter, $features, $resultSetPrototype, $sql); } public function fetchAll() { return $this->select(); } }
Евгений Вдовенко
Физик по образованию, занимаюсь веб-разработкой на PHP с 2013 года. Нравится ковырятся в Zend Framework/Expressive и Joomla CMS/Framework. В процессе разборок с Python. Подумываю посмотреть Go. Считаю PostgreSQL крутым продуктом и внедряю его везде, где могу.