TableGateway и класс модели
TableGateway (шлюз таблицы) — объект пакета laminas-db, который умеет выбирать, вставлять, изменять и удалять строки одной таблицы. Его оборачивают в собственный класс с понятными методами — find(), save(), — и контроллер работает уже с ним. Классы для работы с данными лежат в module/<Модуль>/src/Model.
Класс записи
Один объект этого класса — одна строка таблицы posts. Создайте файл вручную:
// module/Blog/src/Model/Post.php
namespace Blog\Model;
class Post
{
public ?int $id = null;
public string $title = '';
public string $body = '';
public function exchangeArray(array $data): void
{
$this->id = isset($data['id']) ? (int) $data['id'] : null;
$this->title = $data['title'] ?? '';
$this->body = $data['body'] ?? '';
}
}Метод exchangeArray() заполняет объект из массива — строки таблицы. Laminas вызывает его сам для каждой найденной строки.
Класс таблицы
// module/Blog/src/Model/PostTable.php
namespace Blog\Model;
use Laminas\Db\TableGateway\TableGateway;
class PostTable
{
public function __construct(private TableGateway $gateway) {}
public function fetchAll(): array
{
return iterator_to_array($this->gateway->select());
}
public function find(int $id): ?Post
{
return $this->gateway->select(['id' => $id])->current() ?: null;
}
}select() без аргументов возвращает все строки, а с массивом — строки, где столбец равен значению. Значения из массива уходят в базу параметрами, поэтому вклеивать их в SQL не нужно.
Фабрика
Шлюзу нужны имя таблицы и адаптер — это не сервисы с типами, поэтому фабрику пишут вручную:
// module/Blog/src/Model/PostTableFactory.php
namespace Blog\Model;
use Laminas\Db\Adapter\AdapterInterface;
use Laminas\Db\ResultSet\ResultSet;
use Laminas\Db\TableGateway\TableGateway;
use Psr\Container\ContainerInterface;
class PostTableFactory
{
public function __invoke(ContainerInterface $container): PostTable
{
$resultSet = new ResultSet();
$resultSet->setArrayObjectPrototype(new Post());
$gateway = new TableGateway('posts', $container->get(AdapterInterface::class), null, $resultSet);
return new PostTable($gateway);
}
}ResultSet с образцом new Post() превращает каждую строку результата в объект Post. Зарегистрируйте класс в module/Blog/config/module.config.php:
'service_manager' => [
'factories' => [
Model\PostTable::class => Model\PostTableFactory::class,
],
],Вызов из контроллера
// module/Blog/src/Controller/PostController.php
use Blog\Model\PostTable;
public function __construct(private PostTable $posts) {}
public function indexAction()
{
return new ViewModel(['posts' => $this->posts->fetchAll()]);
}
public function viewAction()
{
$post = $this->posts->find((int) $this->params()->fromRoute('id'));
if ($post === null) {
return $this->notFoundAction();
}
return new ViewModel(['post' => $post]);
}Контроллер зарегистрирован через ReflectionBasedAbstractFactory, поэтому PostTable попадёт в конструктор без изменения настроек. Статьи теперь объекты: в шаблонах вместо $post['title'] пишут $post->title. Откройте /blog — список пришёл из базы.
Запись, изменение и удаление
Добавьте в PostTable ещё два метода:
public function save(Post $post): void
{
$data = ['title' => $post->title, 'body' => $post->body];
if ($post->id === null) {
$this->gateway->insert($data);
$post->id = (int) $this->gateway->getLastInsertValue();
return;
}
$this->gateway->update($data, ['id' => $post->id]);
}
public function delete(int $id): void
{
$this->gateway->delete(['id' => $id]);
}У новой статьи id пустой — выполняется insert(), иначе update(). Второй аргумент update() и аргумент delete() — условие, какие строки затронуть.
Частая ошибка
Вызов $this->gateway->update($data) без второго аргумента изменит все строки таблицы, а delete([]) с пустым условием удалит все. Всегда передавайте условие с id и проверяйте, что id не null.