Модель
******
По мере роста нашего приложения мы вскоре обнаружим, что похожие операции с базой данных нужно выполнять в разных местах и в разных презентерах, например получать последние опубликованные записи. Если мы улучшим приложение, например добавив к записям признак того, черновики ли они, нам придётся пройтись по всем местам приложения, где записи достаются из базы данных, и добавить условие `where`, чтобы выбирались только не-черновики.
В этот момент прямой работы с базой данных становится недостаточно, и умнее будет использовать новый метод, возвращающий опубликованные записи. А когда мы позже добавим ещё одно условие (например, не показывать записи с будущей датой), править код придётся только в одном месте.
Метод мы поместим в класс `PostFacade` и назовём его `getPublicArticles()`.
Наш класс модели `PostFacade`, который позаботится о наших записях, мы создадим в каталоге `app/Model/`:
```php .{file:app/Model/PostFacade.php}
<?php
namespace App\Model;
use Nette;
final class PostFacade
{
public function __construct(
private Nette\Database\Explorer $database,
) {
}
public function getPublicArticles()
{
return $this->database
->table('posts')
->where('created_at < ', new \DateTime)
->order('created_at DESC');
}
}
```
В классе мы через конструктор запрашиваем [Explorer |api:Nette\Database\Explorer] базы данных. Тем самым мы пользуемся силой [DI-контейнера |dependency-injection:passing-dependencies].
Перейдём к `HomePresenter`, который изменим так, чтобы избавиться от зависимости от `Nette\Database\Explorer` и заменить её новой зависимостью от нашего нового класса.
```php .{file:app/Presentation/Home/HomePresenter.php}
<?php
namespace App\Presentation\Home;
use App\Model\PostFacade;
use Nette;
final class HomePresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private PostFacade $facade,
) {
}
public function renderDefault(): void
{
$this->template->posts = $this->facade
->getPublicArticles()
->limit(5);
}
}
```
В секции `use` у нас есть `App\Model\PostFacade`, так что в PHP-коде запись можно сократить до `PostFacade`. Этот объект мы запрашиваем в конструкторе, записываем в свойство `$facade` и используем в методе `renderDefault`.
Последний шаг - научить DI-контейнер этот объект порождать. Обычно это делается добавлением пункта в файл `config/services.neon` в секцию `services` с указанием полного имени класса и параметров конструктора. Тем самым он регистрируется, и объект тогда называют **сервисом**. Благодаря волшебству [autowiring |dependency-injection:autowiring] нам обычно не нужно указывать параметры конструктора, потому что DI распознает и передаст их автоматически. Так что хватило бы указать только имя класса:
```neon .{file:config/services.neon}
...
services:
- App\Model\PostFacade
```
Впрочем, добавлять эту строку тоже не нужно. В секции `search` файла `services.neon` определено, что все классы, оканчивающиеся на `-Facade` или `-Factory`, DI найдёт сам, а это относится и к `PostFacade`.
Итоги
=====
Класс `PostFacade` просит в своём конструкторе `Nette\Database\Explorer`, и поскольку этот класс зарегистрирован в DI-контейнере, контейнер создаёт этот экземпляр и передаёт его. DI тем самым создаёт за нас экземпляр `PostFacade` и передаёт его в конструктор класса `HomePresenter`, который его запросил. Это как матрёшка. :) Каждый лишь говорит, что ему нужно, и его не волнует, где и как это создаётся. Созданием занимается DI-контейнер.
.[note]
Здесь можно подробнее почитать о [внедрении зависимостей |dependency-injection:introduction] и [конфигурации |nette:configuring].
{{priority: -1}}
Модель
По мере роста нашего приложения мы вскоре обнаружим, что похожие
операции с базой данных нужно выполнять в разных местах и в разных
презентерах, например получать последние опубликованные записи. Если
мы улучшим приложение, например добавив к записям признак того,
черновики ли они, нам придётся пройтись по всем местам приложения, где
записи достаются из базы данных, и добавить условие where, чтобы
выбирались только не-черновики.
В этот момент прямой работы с базой данных становится недостаточно, и
умнее будет использовать новый метод, возвращающий опубликованные
записи. А когда мы позже добавим ещё одно условие (например, не
показывать записи с будущей датой), править код придётся только в
одном месте.
Метод мы поместим в класс PostFacade и назовём его
getPublicArticles().
Наш класс модели PostFacade, который позаботится о наших записях,
мы создадим в каталоге app/Model/:
<?php
namespace App\Model;
use Nette;
final class PostFacade
{
public function __construct(
private Nette\Database\Explorer $database,
) {
}
public function getPublicArticles()
{
return $this->database
->table('posts')
->where('created_at < ', new \DateTime)
->order('created_at DESC');
}
}
В классе мы через конструктор запрашиваем Explorer базы данных. Тем самым
мы пользуемся силой DI-контейнера.
Перейдём к HomePresenter, который изменим так, чтобы избавиться от
зависимости от Nette\Database\Explorer и заменить её новой зависимостью от
нашего нового класса.
<?php
namespace App\Presentation\Home;
use App\Model\PostFacade;
use Nette;
final class HomePresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private PostFacade $facade,
) {
}
public function renderDefault(): void
{
$this->template->posts = $this->facade
->getPublicArticles()
->limit(5);
}
}
В секции use у нас есть App\Model\PostFacade, так что в PHP-коде
запись можно сократить до PostFacade. Этот объект мы запрашиваем в
конструкторе, записываем в свойство $facade и используем в методе
renderDefault.
Последний шаг – научить DI-контейнер этот объект порождать. Обычно
это делается добавлением пункта в файл config/services.neon в секцию
services с указанием полного имени класса и параметров
конструктора. Тем самым он регистрируется, и объект тогда называют
сервисом. Благодаря волшебству autowiring нам обычно не нужно
указывать параметры конструктора, потому что DI распознает и передаст
их автоматически. Так что хватило бы указать только имя класса:
...
services:
- App\Model\PostFacade
Впрочем, добавлять эту строку тоже не нужно. В секции search файла
services.neon определено, что все классы, оканчивающиеся на
-Facade или -Factory, DI найдёт сам, а это относится и к
PostFacade.
Итоги
Класс PostFacade просит в своём конструкторе Nette\Database\Explorer, и
поскольку этот класс зарегистрирован в DI-контейнере, контейнер
создаёт этот экземпляр и передаёт его. DI тем самым создаёт за нас
экземпляр PostFacade и передаёт его в конструктор класса
HomePresenter, который его запросил. Это как матрёшка. :) Каждый лишь
говорит, что ему нужно, и его не волнует, где и как это создаётся.
Созданием занимается DI-контейнер.
Здесь можно подробнее почитать о внедрении зависимостей и конфигурации.