# Основы

# Создание приложения

# Загрузчик

Точка входа в приложение располагается в файле public/index.php. На него будут перенапрвляться все запросы (кроме запросов к статичным ресурсам). Это позволяет повысить безопасность и обеспечить работу маршрутизации. Содержимое загрузчика:

<?php

$loader = require __DIR__ .'/../vendor/autoload.php';
$loader->setPsr4('App\\', __DIR__.'/../app');

$mode = 'dev';
(new App\App())->run($mode);

Примечание

В стандартной комплектации уже содержатся файлы настроек веб-сервера. Но их нужно будет исправить, если вы хотите разместить загрузчик и в другом месте или использовать несколько загрузчиков.

# Режим запуска

Переменная $mode отвечает за режим запуска приложения. В режиме dev включена отладка и отображаются подробные сообщения об ошибках. Не забудьте изменить режим на prod при развертывании на рабочем (production) сервере.

Примечание

В файлах конфигурации config/configDev.php и config/configProd.php можно указывать настройки специфичные для определенного режима запуска. Подробнее о конфигурации.

# Класс приложения

Класс приложения позволяет управлять настройками и зависимостями, а также служит точкой входа для маршрутизации. Создадим файл App/App.php следующего содержания:

<?php
namespace App;

use Pandora3\Core\Application\Application;

class App extends Application {
	
}

Примечание

Вместо app\App в качестве имени класса и пространства имен можно использовать и другие идентификаторы, главное чтобы им соответствовали названия файла и каталога. Не забудьте также изменить значения в public/index.php.

# Контейнер зависимостей

При инициализации класс приложения создает контейнер зависимостей $this->container. Переопределив метод dependencies в классе приложения можно добавлять и переопределять зависимости.

protected function dependencies(Container $container): void {
	parent::dependencies($container);
	$container->setShared(DatabaseConnectionInterface::class, EloquentConnection::class);
}

Примечание

Если требуется отключить стандартные зависимости, необходимо убрать вызов parent::dependencies

# Стандартные зависимости

При наследовании класса приложения от Pandora3\Core\Application\Application, он получит следующие зависимости:

  • Request реализующий RequestInterface
  • Router реализующий RouterInterface
  • DatabaseConnection реализующий DatabaseConnectionInterface

# Маршрутизация

В файле app/routes.php опишем необходимые маршруты:

<?php

return [
	'/*' => \App\Controllers\HomeController::class,
	'/books/*' => \App\Controllers\BooksController::class
];

В качестве ключей укажем маршруты, а в качестве значений имена классов контроллеров. При указании адреса можно использовать * для обозначения произвольных частей адреса. В качестве значения вместо класса контроллера можно указать любой класс который поддерживает интерфейс RouteInterface или функцию-замыкание.

Примечание

Если не указывать * на конце маршрута, он будет срабатывать полько при строгом соответствии. Но при этом будет невозможно использовать вложенную маршрутизацию.

# Контроллеры

Создание контроллера App/Controllers/BookController.php выглядит следующим образом:

<?php
namespace App\Controllers;

use Pandora3\Core\Controller\Controller;

class BookController extends Controller {
	
	public function getRoutes(): array {
		return [
			'/' => 'books',
			'/add' => 'add'
		];
	}
	
	protected function books() {
		return $this->render('Books');
	}
	
	protected function add() {
		return $this->render('Form');
	}
	
}

Метод getRoutes возвращает маршруты в виде массива, где в качестве значений указаны имена методов. Аналогичным образом создадим и другие контроллеры.

# Представления

По умолчанию представления находятся в каталоге app/Views. Пример представления Home.php:

<!DOCTYPE html>
<html>
	<head></head>
	<body>
		<h1>Hello, world</h1>
	</body>
</html>

# Макеты

Как правило в макете есть блоки, присутствующие на всех страницах (шапка, боковые меню, подвал и т.д.). Поэтому удобнее вынести их в отдельный файл app/Views/Layout/Main.php:

<!DOCTYPE html>
<html>
	<head></head>
	<body>
		<header>Header</header>
		<nav>Navigation</nav>
		<main><?= $content ?></main>
		<footer>Footer</footer>		
	</body>
</html>

В остальных представлениях теперь достаточно оставить только уникальную часть:

<h1>Hello, world</h1>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.</p>

Можно создать и другие макеты (например для печати), и применить один из них перед вызовом метода render в контроллере.

protected function articlePrint() {
	// ...
	$this->setLayout('Print');

	return $this->render('Article');
}

В результате получилось минимальное веб-приложение. Пора проверить его работоспособность в браузере!

# Модели

Модели предоставляют объектный способ работы с базой данных, реализуя подход ORM. На данный момент доступна работа с БД с помощью Eloquent(opens new window) (библиотека входящая в состав фреймворка Laravel(opens new window) ). Каждая таблица имеет соответствующий класс-модель, который используется для работы с этой таблицей. Модели позволяют запрашивать данные из таблиц, а также вставлять в них новые записи. В будущем будут добавлены адаптер для Doctrine (из Symfony) и проработанное нативное решение.

# Создание моделей Eloquent

Для начала создадим модель Eloquent. Модели обычно располагаются в директории app. Но возможно поместить их в любое место, в котором работает автозагрузчик. Все модели Eloquent наследуют класс Illuminate\Database\Eloquent\Model.

Рассмотрим на примере модели Employee.php, которая используется для получения и хранения информации из таблицы базы данных о сотрудниках. Имя таблицы можно указать явно:

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Employee extends Model {
 	/**
	 * Таблица, связанная с моделью.
	 *
	 * @var string
	 */
	 protected $table = 'employee';
}

Примечание

Если имя таблицы не указано явно, то в соответствии с принятым соглашением будет использовано имя класса в нижнем регистре (snake_case) и во множественном числе.

# Первичные ключи

Eloquent предполагает, что каждая таблица имеет первичный ключ с именем id. Можно определить свойство $primaryKey для указания другого имени. Предполагается, что первичный ключ является инкрементным числом, и автоматически будет приведёт к типу int. Для использования неинкрементного или нечислового первичного ключа необходимо задать открытому свойству $incrementing значение false. В приимере зададим первичный ключ с именем userId.

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Employee extends Model {
	 /**
	 * Таблица, связанная с моделью.
	 *
	 * @var string
	 * @var int
	 /
	 protected $table = 'employee';
	 protected $primaryKey = 'userId';
}

# Отметки времени

По умолчанию в моделях Eloquent предполагается, что в таблице есть поля меток времени (timestamp) — created_at и updated_at. Чтобы они не обрабатывались автоматически, установим свойство $timestamps в false. В противном случае в таблице будут присутствовать поля меток времени (timestamp) — created_at и updated_at. Например, в модели User.php присутствуют метки времени и это свойство установлено в true:

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Pandora3\Libs\Application\Application;

class User extends Model {
	/**
	 * Таблица, связанная с моделью. 
	 *
	 * @var string
	 protected $table = 'user';
	 
	 * @var bool
	 public $timestamps = true;
	 */
}

# Соединение с БД

По умолчанию модели Eloquent будут использовать основное соединение с БД, настроенное для приложения. Если есть необходимость указать другое соединение для модели, то надо использовать свойство $connection. Например, укажем в модели User.php отличное от основного соединение:

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model {
	/**
	 * Название соединения для модели.
	 *
	 * @var string
	 */
	 protected $connection = 'testUser';
}

# Работа с моделями

После создания модели и связанной с ней таблицы, можно начать работать с данными из базы. Каждая модель Eloquent представляет собой мощный конструктор запросов, позволяющий удобно выполнять запросы к связанной таблице. Для примера опять обратимся к модели User.php:

<?php
use App\Models;

protected function printUser() {
	 $users = App\Models\Users::all();
	 foreach ($users as $user)
		 echo $user->login;
}

Метод all в Eloquent возвращает все результаты из таблицы модели. Поскольку модели Eloquent работают как конструктор запросов, то возможно добавить ограничения в запрос, а затем использовать метод get для получения результатов.

Такой метод Eloquent, как all, получающий несколько результатов, возвращает экземпляр Illuminate\Database\Eloquent\Collection. Класс Collection предоставляет множество полезных методов для работы с результатами Eloquent. Например, можно перебирать такую коллекцию в цикле как массив (предыдущий пример).

# Получение одиночных записей из базы даннных

Кроме получения всех записей указанной таблицы можно также получить конкретные записи с помощью find или first. Вместо коллекции моделей эти методы возвращают один экземпляр модели:

<?php

// Получение модели по её первичному ключу
$user = App\Models\Users::find(1);

// Получение первой модели, удовлетворяющей условиям
$user = App\Models\Users::where('id', 1)->first();

Также можно вызвать метод find с массивом первичных ключей, который вернёт коллекцию подходящих записей:

<?php

$user = App\Models\Users::find([1, 2, 3]);

Методы findOrFail и firstOrFail получают первый результат запроса. А если результатов не найдено, выбрасывается исключение Illuminate\Database\Eloquent\ModelNotFoundException. Пример можно увидеть в функции обновления пользователя.

protected function update() {
	 $id = (int) $this->request->get('id');
	 $user = User::findOrFail($id);
	 // Если пользователь найден, то далее следует обновление данных
}

Примечание

Если в таблице не найдены записи, удовлетворяющие условию, то будет обработано исключение Не найдено".

# Встака и изменение моделей

Для создания новой записи в БД надо создать экземпяр модели, задать атрибуты модели и вызвать метод save или saveOrFail. Рассмотрим на примере создания нового экземпляра студента.

<?php
namespace App\Plugins\Students\Controllers;

use App\Models;

class StudentController extends Controller {
	/**
	 * Создание нового экземпляра студента
	 *
	 * @param  Request  $request
	 * @return Response
	 */
	
	 protected function add(): {
		\DB::beginTransaction();
		$student = new Student( array_replace($form->studentValues, ['userId' => $user->id]) );
		$student->saveOrFail();
		\DB::commit();
}

При вызове метода save запись будет вставлена в таблицу, содержащую информацию о студентах. Отметки времени created_at и updated_at будут автоматически установлены при вызове save, поэтому их не нужно задавать вручную.

# Удаление моделей

Для удаления модели нужно вызвать метод delete на ее экземпляр.

<?php
namespace App\Plugins\Students\Controllers;

use App\Models\Student;

class StudentController extends Controller {
	$student = Student::find($id);
	$student->delete();
}

В итоге будет удалена информация о студенте с заданным id.

# Виджеты

Использование виджетов удобно при необходимости разбивать вывод информации страницы на определенные блоки. Например меню, сайдбар, слайдер, форму подписки и тд. При этом, лучшим решением будет сделать такие блоки максимально автономными - со своим классом, шаблоном и тд. То есть сделать данные блоки отдельными виджетами, которые затем можно легко подключать в любом нужном шаблоне. Это помогает разгрузить контроллер от дополнительного кода, сгруппировать файлы виджетов в одном месте, легко поддерживать их код и использовать одинаковый, простой синтаксис вызова нужного блока.

По умолчанию виджеты располагаются в пространстве имен App\Widgets. Хотя использование пространства имен по умолчанию очень удобно, в некоторых случаях может потребоваться больше гибкости. Например, большое количество виджетов имеет смысл сгруппировать в папки с пространством имен.

# Создание виджета и работа с ним.

Создание рассмотрим на примере виджета, отвечающего за меню. Файлы виджетов создадим в папке App\Widgets. Класс виджета должен иметь соответствующее пространство имен: namespace App\Widgets. Если виджет должен, для своей работы, получить какие-то данные из контроллера и тд. (передаются в шаблоне), то необходимо предусмотреть метод конструктор для класса виджета с получением аргумента в виде массива параметров.

<?php
namespace App\Widgets\Menu;

use App\Models\Directions\Direction;
use App\Models\Examination\ExaminationMark;
use App\Models\FinanceHelp\FinanceHelp;
use App\Models\Stipend\Stipend;
use App\Models\StudentGroups\StudentGroup;
use App\Models\Students\Student;
use App\Models\StudyPlans\StudyPlan;
use App\Models\Users\User;
use Pandora3\Libs\Widget\Widget;

class Menu extends Widget {

}

В файле config\widgets.php находится массив, в котором, в качестве ключей нужно указать названия для создаваемых виджетов, а в качестве значений названия классов виджетов (с пространством имен). Например:

'test' => 'App\Widgets\TestWidget'

Классы для своих виджетов нужно создавать в папке app\Widgets. Для размещения шаблонов виджетов предназначена папка app\Widgets\views.

Класс виджета должен иметь соответствующее пространство имен: namespace App\Widgets. Так же класс виджета должен включать интерфейс ContractWidget и реализовывать его метод execute(). Если виджет должен, для своей работы, получить какие-то данные из контроллера и тд. (передаются в шаблоне), то необходимо предусмотреть метод конструктор для класса виджета с получением аргумента в виде массива параметров.

# Передача переменных в виджет

3

# Создание приложения

# Пример виджета с передачей параметров.

Рассмотрим создание виждета, отвечающего за меню.

<?php
namespace App\Widgets\Menu;

 // В качестве примера указаны только основные модели
use App\Models\Directions\Direction;
use App\Models\StudyPlans\StudyPlan;
use App\Models\Users\User;
use Pandora3\Libs\Widget\Widget;

class Menu extends Widget {
	public function __construct(string $uri, array $context = []) {
		$this->uri = $uri;
		parent::__construct($context);
	}
	
		protected function getItems() {
		$items = [
			[
				'uri' => '/users',
				'visible' => function(User $user) {
					return $user->can('view', User::class);
				},
				'title' => 'Пользователи', 'icon' => '<i class="mdi mdi-shield-account"></i>'
			],
			[
				'uri' => '/directions',
				'visible' => function(User $user) {
					return $user->can('view', Direction::class) && !$user->isOperatorEntrance();
				},
				'title' => 'Направления', 'icon' => '<i class="mdi mdi-folder-text"></i>'
			],
			[
				'uri' => '/study-plans',
				'visible' => function(User $user) {
					return $user->can('view', StudyPlan::class) && !$user->isOperatorEntrance();
				},
				'title' => 'Учебные планы', 'icon' => '<i class="mdi mdi-school"></i>'
			],
			// Аналогично задаются другие пункты меню: Успеваемость, Группы, Студенты, Успеваемость, Стипендии, Материальная помощь
		];

		return $items;
	}

# Библиотеки

Библиотеки — это классы, выполняющие действия, не привязанные к конкретному проекту. Например, это может быть библиотека для создания PDF-документов из HTML. Эта задача не специфична конкретно для какого-то проекта — такие вещи и называются «библиотеками».

# Плагины