Паттерн Active Record в PHP

Реализуем Active Record в PHP

Сегодня мы изучим ещё один паттерн проектирования – Active Record. Этот шаблон говорит о том, что сущность (объекты класса статьи или пользователя) сами должны управлять работой с базой данных. То есть весь остальной код, который эти сущности использует, не должен знать о базе данных. Наши контроллеры не должны работать с базой данных, получая данные и заполняя ими сущности. Они должны знать только о сущностях. Сущность сама должна позаботиться о работе с базой данных. О том, как это реализовать – читайте далее.

Для начала нужно вообще понять, как стоит работать с сущностями при помощи такой концепции. Самое простое, что мы можем реализовать – это чтение из базы данных. И мы должны сделать это, обращаясь напрямую к сущностям-объектам. То есть мы должны сказать: «Эй, Article, дай мне все статьи». Но согласитесь, глупо будет для этого создать сущности, а после этого попросить чтобы они заполнили себя данными из базы. Нам нужно сделать это как-то по другому. Например, обратиться к сущности, не создавая её, но чтобы она при этом вернула нам созданные сущности. Вспоминаем статические методы – их ведь можно вызывать, не создавая объекта. То, что нам нужно!

Давайте добавим в Article статический метод, возвращающий нам все статьи.

src/MyProject/Models/Articles/Article.php

Код доступен только после покупки курса ООП в PHP.

Теперь, чтобы получить статьи в контроллере, нам нужно сделать следующее:

src/MyProject/Controllers/MainController.php

Код доступен только после покупки курса ООП в PHP.

Заметили, как сразу упростился наш контроллер? Пропала зависимость от базы данных. Если вы сейчас попробуете выполнить этот код, то он успешно отработает.

А теперь давайте добавим на код этого статического метода.

Код доступен только после покупки курса ООП в PHP.

Согласитесь, можно заменить Article::class на self::class – и сюда автоматически подставится класс, в котором этот метод определен. А можно заменить его и вовсе на static::class – тогда будет подставлено имя класса, у которого этот метод был вызван. В чём разница? Если мы создадим класс-наследник SuperArticle, он унаследует этот метод от родителя. Если будет использоваться self:class, то там будет значение “Article”, а если мы напишем static::class, то там уже будет значение “SuperArticle”. Это называется поздним статическим связыванием – благодаря нему мы можем писать код, который будет зависеть от класса, в котором он вызывается, а не в котором он описан.

Итак, давайте изменим этот метод:

Код доступен только после покупки курса ООП в PHP.

А теперь давайте попробуем избавиться от зависимости от таблицы “articles”. Вынесем получение названия таблицы в отдельный метод. Вот так:

Код доступен только после покупки курса ООП в PHP.

А теперь внимательно посмотрите на содержимое класса Article. Не кажется ли вам, что методы findAll(), __set(), underscoreToCamelCase() можно вот хоть сейчас взять и скопировать в сущность User, и начать их использовать? Только не нужно ничего копировать, мы ведь пишем на объектно-ориентированном языке, и можем использовать наследование! Мы можем просто вынести всю эту логику в отдельный класс, а там где она нужна, просто от него наследоваться. Давайте так и поступим.

Создадим отдельный класс, реализующий всю эту логику.

src/MyProject/Models/ActiveRecordEntity.php

Код доступен только после покупки курса ООП в PHP.

Так как создание самого этого класса нам не нужно, то делаем его абстрактным. А теперь переносим в него универсальный код из класса Article.

Код доступен только после покупки курса ООП в PHP.

Давайте по порядку.

  • добавили protected-свойство ->id и public-геттер для него – у всех наших сущностей будет id, и нет необходимости писать это каждый раз в каждой сущности – можно просто унаследовать;
  • перенесли public-метод __set() – теперь все дочерние сущности будут его иметь
  • перенесли метод underscoreToCamelCase(), так как он используется внутри метода __set()
  • public-метод findAll() будет доступен во всех классах-наследниках
  • и, наконец, мы объявили абстрактный protected static метод getTableName(), который должен вернуть строку – имя таблицы. Так как метод абстрактный, то все сущности, которые будут наследоваться от этого класса, должны будут его реализовать. Благодаря этому мы не забудем его добавить в классах-наследниках.

Давайте теперь посмотрим на то, во что у нас превратится класс Article. Наследуемся от полученного класса и убираем лишнее. Обратите внимание, свойства теперь становятся не private, а protected, чтобы к ним можно было достучаться из класса-родителя.

src/MyProject/Models/Articles/Article.php

Код доступен только после покупки курса ООП в PHP.

Вот так вот он у нас значительно упростился. Проверим, что всё работает. И... Всё работает!
Давайте теперь добавим метод, который будет возвращать одну статью по id. Проще простого! Добавляем в наш класс ActiveRecordEntity ещё один метод getById().

src/MyProject/Models/ActiveRecordEntity.php

Код доступен только после покупки курса ООП в PHP.

Этот метод вернёт либо один объект, если он найдётся в базе, либо null – что будет говорить об его отсутствии.

Тогда наш контроллер статей, где мы получаем только одну статью приведется к виду:

src/MyProject/Controllers/ArticlesController.php

Код доступен только после покупки курса ООП в PHP.

А шаблон станет таким:

templates/articles/view.php

Код доступен только после покупки курса ООП в PHP.

А теперь давайте в нашем классе User добавим свойства, которые будут соответствовать его полям в базе данных.

src/MyProject/Models/Users/User.php

Код доступен только после покупки курса ООП в PHP.

А теперь внимание, просто наследуемся от нашего ActiveRecordEntity и получаем все эти возможности, что есть у сущности Article! Просто добавляем несколько строк и указываем нужную таблицу, где хранятся пользователи.

src/MyProject/Models/Users/User.php

Код доступен только после покупки курса ООП в PHP.

Да это же магия! =)
Попробуем вывести автора статьи, для этого у статьи добавляем геттер для этого поля:

src/MyProject/Models/Articles/Article.php

Код доступен только после покупки курса ООП в PHP.

Добавляем в контроллере получение нужного юзера:

src/MyProject/Controllers/ArticlesController.php

Код доступен только после покупки курса ООП в PHP.

И выводим никнейм автора в шаблоне:

templates/articles/view.php

Код доступен только после покупки курса ООП в PHP.

Смотрим на результат:

Вывод автора статьи

Круто, да? Но можно ещё круче! Можно ведь попросить статью давать нам не id автора, а сразу автора! Для этого просто меняем геттер в статье:

src/MyProject/Models/Articles/Article.php

Код доступен только после покупки курса ООП в PHP.

Вот так просто! Прямо в геттере просим сущность юзера выполнить запрос в базу и получить нужного пользователя, по id, который хранится в статье. При этом запрос будет выполнен только если мы вызовем этот геттер, это называется LazyLoad (ленивая загрузка) – это когда данные не подгружаются до тех пор, пока их не запросят.

Код нашего контроллера снова упрощается:

src/MyProject/Controllers/ArticlesController.php

Код доступен только после покупки курса ООП в PHP.

А в шаблоне мы можем напрямую запросить пользователя:

templates/articles/view.php

Код доступен только после покупки курса ООП в PHP.

Насыщенный получился урок. Надеюсь, всё было понятно. Если нет – вы знаете, что я всегда подскажу, не стесняйтесь, обращайтесь. До следующего урока!