Перейдём к рассмотрению вопроса работы с базой данных. В настоящее время нет необходимости писать низкоуровневые SQL-CRUD-запросы для работы с сущностями базы данных, для этих целей созданы ORM (Object-Relational Mapping) обёртки, которые позволяют делать это легко и удобно в стиле ООП. Хотя все рассматриваемые нами фреймворки (а именно: Yii, Symfony и Laravel) не ограничивают нас в средствах работы с базой данных (то есть, если у нас сложная структура БД или нам нравится на каждое действие писать чистый SQL, то мы можем это делать беспрепятственно), однако имеют в своих арсеналах конкретные реализации ORM, которые заметно облегчают взаимодействие с БД.
ActiveRecord в Yii
Чтобы получить данные из таблицы company, нам требуется лишь создать файл common/models/Company.php и поместить в него следующий код:
<?php
namespace common\models;
use yii\db\ActiveRecord;
class Company extends ActiveRecord
{
}
Это достаточно, чтобы получить доступ к данным и уже начать их использовать, но так как мы создали таблицу во множественном числе: companies, а фреймворк будет искать по умолчанию таблицу company, то нам нужно переопределить метод tableName():
<?php
...
class Company extends ActiveRecord
{
public static function tableName(): string
{
return '{{%companies}}';
}
}
Если обернуть название таблицы в двойные фигурные скобки, то это поможет предотвратить возможные коллизии с именем таблицы при формировании SQL запросов. Если перед названием таблицы поставить знак процента %, то вместо него в запросе автоматически будет подставлен префикс для таблиц (если он указан в настройках приложения).
Класс модели, как можно было заметить, мы поместили не в папку frontend, а в папку common, так как данный класс является «общим», то есть может быть использован не только для пользовательской части нашего приложения, но в будущем и для админ-части.
Чтобы класс был идентифицирован фреймворком, как класс-модель, нужно чтобы он наследовался от yii\db\ActiveRecord.
Теперь можно использовать данный класс для получения данных в контроллере:
<?php
...
use common\models\Company;
class CompanyController extends Controller
{
/**
* Displays list of companies.
*
* @return string
*/
public function actionIndex(): string
{
$companies = Company::find()->orderBy('name')->all();
$result = [];
foreach ($companies as $company) {
$result[] = 'ID: ' . $company->id . '. Company name: ' . $company->name;
}
return implode("<br>", $result);
}
}
Хорошим правилом при создании классов-моделей является указание списка полей, имеющихся в таблице, в виде phpDoc-комментария для класса.
<?php
...
/**
* Company model
*
* @property integer $id
* @property string $name
* @property string $description
* @property timestamp $created_at
* @property timestamp $updated_at
*/
class Company extends ActiveRecord
{
...
}
Осталось создать аналогичные классы для остальных сущностей. Перейдём к рассмотрению ORM, которая идёт по-умолчанию с Symfony.
Doctrine в Symfony
В качестве ORM фреймворк Symfony по-умолчанию предлагает использовать Doctrine. Однако не навязывает её использование, можно вовсе обойтись без неё, а можно использовать какую-либо иную ORM, например, Propel. Но в рамках приложения FunnyApp мы будем использовать именно Doctrine.
В Doctrine есть такой инструмент, как reverse engineering, который позволяет нам сгенерировать классы-сущности по готовой структуре базы данных.
Для этого у нас уже должна быть создана структура базы данных, должны быть заданы все необходимые связи между таблицами, а также, конечно же, должно быть настроено соединение с нашей БД. После этого в папке проекта нужно выполнить 3 консольные команды:
php bin/console doctrine:mapping:import --force AppBundle yml
php bin/console doctrine:mapping:convert annotation ./src
php bin/console doctrine:generate:entities AppBundle
Первая команда сгенерирует нам yml файлы, которые будут описывать структуру каждой таблицы и её связи. Вторая команда должна сгенерировать классы-сущности… Однако в рамках той структуры приложения, которая у нас сейчас есть, мы получаем: No Metadata Classes to process. В чём проблема? Может надо не ./src, а полный путь указать до сгенерированных файлов? Нет, не помогает… Можно погуглить, поискать на Stack Overflow, но мне ничего так и не помогло… Пока не заметил я одну деталь: по-умолчанию Symfony сгенерировала нам AppBundle, который положила сразу в ./src, однако, если посмотреть повнимательнее на то, что написано в документации по reverse engineering, то можно заметить, что в консольных командах используется «полное» название бандла: AcmeBlogBundle, а у нас «обрезанный» бандл без префикса (в данном случае Acme), который, как правило, указывает на вендора. Как оказалось, именно из-за этого вторая команда и не выполняется корректно, то есть скрипт не находит файлы, из которых надо делать сущности.
Как сгенерировать и зарегистрировать в системе новый бандл можно прочитать в документации:
http://symfony.com/doc/current/bundles.html
А мы подкорректируем наш «обрезанный» бандл и поместим его в папку Funny. Таким образом наш бандл будет лежать в папке src/Funny/AppBundle и будет иметь пространство имён:
namespace Funny\AppBundle;
Помимо того, что нам нужно переименовать корневой файл бандла src/Funny/AppBundle/AppBundle.php в FunnyAppBundle.php, а также переименовать внутри и название самого класса и поправить неймспейс, не забудем внести поправки в контроллерах бандла, в конфигурации роутов (теперь для идентификации экшена нужно использовать «полное» название бандла FunnyAppBundle) а также поправить код регистрации бандла в app/AppKernel.php.
Итак, удаляем папку src/Funny/AppBundle/Resources, если мы её ещё не удалили и запускаем команды:
php bin/console doctrine:mapping:import --force FunnyAppBundle yml
php bin/console doctrine:mapping:convert annotation ./src
php bin/console doctrine:generate:entities FunnyAppBundle
Итак, первая команда создаёт yml файлы, описывающие структуру таблиц и их связи в базе данных, вторая команда создаёт классы-сущности, согласно данным из yml файлов, а третья команда создаёт getter’ы и setter’ы для полей наших сущностей.
Теперь можно в контроллере получить данные из базы и отобразить их пользователю. В качестве примера выведем список компаний в экшене FunnyAppBundle:Company:list. Для этого нам потребуется файл src/Funny/AppBundle/Controller/CompanyController.php.
<?php
...
class CompanyController extends Controller
{
/**
* Displays list of companies.
*
* @return Response
*/
public function listAction(): Response
{
$repository = $this->getDoctrine()->getRepository('FunnyAppBundle:Companies');
$companies = $repository->findAll();
foreach ($companies as $company) {
$result[] = 'ID: ' . $company->getId() . '. Company name: ' . $company->getName();
}
return new Response(implode("<br>", $result));
}
...
}
Получить все записи из таблицы companies достаточно просто. Сначала получаем объект Doctrine, на котором вызываем метод получения репозитория, который отвечает за таблицу companies, а затем просто вызываем findAll(). Вот и всё, можно пройтись циклом по всем записям и сформировать нужную строку для вывода пользователю. Отметим один момент: в Yii мы обращаемся непосредственно к полям объекта, а тут мы получаем данные через геттеры.
Когда я открыл результат в браузере, оказалось, что вместо русских символов в названиях компаний отображаются «ромбики», что говорит нам о проблемах с кодировкой. Проблема решилась добавлением строк:
options:
1002: "SET NAMES 'UTF8' COLLATE 'utf8_unicode_ci'"
в настройки подключения к базе в файл конфигурации app/config/config.yml.
doctrine:
dbal:
driver: pdo_mysql
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
options:
1002: "SET NAMES 'UTF8' COLLATE 'utf8_general_ci'"
Eloquent в Laravel
Данный фреймворк предлагает «из коробки» использовать ORM Eloquent, которая представляет собой реализацию паттерна ActiveRecord, то есть нам нужно лишь создать класс-модель, который будет наследоваться от Illuminate\Database\Eloquent\Model и можно уже использовать его для получения данных из базы.
Для облегчения создания модели можно воспользоваться консольной командой:
php artisan make:model Models/Company
где Models — это папка, в которой будут храниться наши модели, а Company — название самой модели, которая будет связана с таблицей companies. В итоге получим файл app/Models/Company.php с содержимым:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Company extends Model
{
//
}
По умолчанию действует соглашение, что названию модели в единственном числе Company, соответствует таблица в базе во множественном числе companies. В случае, если название модели состоит из нескольких слов, например, PortfolioItem, то данной модели будет соответствовать таблица в базе, записанная в стиле «snake case» во множественном числе, то есть portfolio_items.
Теперь можно использовать данную модель для получения данных в контроллере app/Http/Controllers/CompanyController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Routing\Controller;
use App\Models\Company;
class CompanyController extends Controller
{
/**
* Displays list of companies.
*
* @return string
*/
public function index(): string
{
$companies = Company::all();
$result = [];
foreach ($companies as $company) {
$result[] = 'ID: ' . $company->id . '. Company name: ' . $company->name;
}
return implode("<br>", $result);
}
...
}