Post on 06-Jan-2017
Примеры
решения
типичных задач
за рамками ядра
Yii2
QuartSoft
YiiSoft
Климов П.В.
Интернационализация статического
текста
I18N
$translations
translate()
“translation
source”
1
*MessageSource
translate()DbMessageSource
PhpMessageSource
Client
“Yii::t()”
Интернационализация сущностей в
базе данных
Itemn
id
canonicalName
price
Language
id
name
locale
Translation
n
itemId
languageId
name
description
Расширение «yii2tech/ar-variation»
VariationBehavior
$variationRelation
$defaultVariationRelation
“attached behavior”
1
*
Item
$canonicalName
Language
ItemTranslation
$name
1
1
1
* “has many”
“has many”
“has many via”
class Item extends \yii\db\ActiveRecord
{
public function behaviors()
{
return [
‘translations’ => [
'class' => VariationBehavior::class,
'variationsRelation' => 'translations',
'variationOptionReferenceAttribute' => 'languageId',
'optionModelClass' => Language::class,
]
];
}
public function getTranslations()
{
return $this->hasMany(ItemTranslation::class, [‘itemId' => 'id']);
}
}
Конфигурация «VariationBehavior»
$model = new Item();
// `getVariationModels()` возвращает список моделей-вариаций
$variations = $model->getVariationModels();
// их количество всегда равно количеству доступных опций:
var_dump(count($variations) == Language::find()->count()); // `true`
// валидация и сохранение производятся автоматически:
$post = Yii::$app->request->post();
if ($model->load($post)
&& Model::loadMultiple($model->getVariationModels(), $post)
&& $model->save())
{
return $this->redirect(['index']);
}
Управление вариаторами
class Item extends \yii\db\ActiveRecord
{
public function behaviors()
{
return [
‘translations’ => [
// …
'defaultVariationRelation' => 'defaultTranslation',
'defaultVariationOptionReference' => function () {
return Yii::$app->language; // ID опции по умолчанию
},
'variationAttributeDefaultValueMap' => [
‘name' => ‘canonicalName‘ // fallback для атрибутов вариатора
],
]
];
}
public function getDefaultTranslation()
{
return $this->hasDefaultVariationRelation(); // `has many` -> `has one`
}
}
Вариация «по умолчанию»
class VariationBehavior extends \yii\base\Behavior
{
public function __get($name)
{
try {
return parent::__get($name);
} catch (\yii\base\UnknownPropertyException $e) {
return $this->getDefaultVariationModel()->{$name};
}
}
public function __set($name, $value) {…}
public function canGetProperty($name, $checkVars = true) {…}
public function canSetProperty($name, $checkVars = true) {…}
}
Добавление виртуальных свойств
«хозяину» поведения
$model = Item::find()->one();
echo $model->name; // вернет `$model->defaultTranslation->name`
// а если `defaultTranslation` не найдено, то `$model->canonicalName`
// Задание нового значения:
$model->name = ‘New translation name’;
// «Жадная» загрузка:
$models = Item::find()->with(‘defaultTranslation’)->all();
foreach ($models as $model) {
echo $model->name;
}
Прямой доступ к атрибутам вариатора
Сущности с общими атрибутами
Преподаватель Студент
- Ученая
степень
- Зарплата
- ФИО
- Паспорт
- Адрес
- Телефон
- Учебная
группа
- Стипендия
Роли в реляционной базе данных
Person
id
name
address
phone Student
personId
studyGroupId
scolarship
Instructor
personId
rankId
salary
Роли в реляционной базе данных
Person
1
id
name
address
phone Student
personId
studyGroupId
scolarship
Can be
1
Instructor
personId
rankId
salary
Can be1 1
Расширение «yii2tech/ar-role»
RoleBehavior
$roleRelation
“attached behavior”
1
Student
Instructor
Person
1
1
1“has one”
“has one”
1
1
“attached behavior”
1
1
class Student extends \yii\db\ActiveRecord
{
public function behaviors()
{
return [
‘translations’ => [
'class' => RoleBehavior::class,
‘roleRelation' => ‘person',
]
];
}
public function getPerson()
{
return $this->hasOne(Person::class, [‘id' => ‘personId']);
}
}
Конфигурация «RoleBehavior»
class RoleBehavior extends \yii\base\Behavior
{
public function __get($name)
{
try {
return parent::__get($name);
} catch (\yii\base\UnknownPropertyException $e) {
return $this->getRoleRelationModel()->{$name};
}
}
public function __call($name, $params)
{
$model = $this->getRoleRelationModel();
if ($model->hasMethod($name)) {
return call_user_func_array([$model, $name], $params);
}
return parent::__call($name, $params);
}
}
Эмуляция наследования
$model = Student::find()->one();
echo $model->name; // вернет `$model->person->name`
// Задание нового значения:
$model->name = ‘John Doe’;
// валидация и сохранение производятся автоматически:
$model->save(); // вызов `$model->person->save()`
$model->locateAddress(); // вызов `$model->person->locateAddress()`
// «Жадная» загрузка:
$models = Student::find()->with(‘person’)->all();
foreach ($models as $model) {
echo $model->name;
}
Прямой доступ к атрибутам роли
Сохранение файлов
Client
Web
Server
HTTP
requests
HDD
File read/write
Распределенные приложения
Client
Load
Balancer
Regular
HTTP
requests
Web
Server 1
Web
Server 2
Internal HTTP requests
HDD 1 HDD 2
File read/write File read/write
Централизованное файловое хранилище
Client
Load
Balancer
Regular
HTTP
requests
Web
Server 1
Web
Server 2
Internal HTTP requests
File
ServerFTP / SFTP / REST
FTP / SFTP / REST
Абстракция файловой системы
sftp\Storage
file\Storage
$buckets
getBucket()
“consists of”
1
*file\Bucket
saveFileContent()
getFileContent()
local\Storage
sftp\Bucket
local\Bucket
Client
“file
r/w”
$bucket = Yii::$app->fileStorage->getBucket('tempFiles');
// создать файл с заданным содержимым:
$bucket->saveFileContent('foo.txt', 'Foo content');
// удалить файл:
$bucket->deleteFile('foo.txt');
// скопировать файл в хранилище:
$bucket->copyFileIn('/path/to/source/file.txt', 'file.txt');
// скопировать файл из хранилища:
$bucket->copyFileOut('file.txt', '/path/to/destination/file.txt');
var_dump($bucket->fileExists('file.txt')); // выводит `true`
echo $bucket->getFileUrl('file.txt'); // выводит:
// http://domain.com/files/f/i/file.txt'
Операции с файлами
Файловые потоки
// Открыть локальный файл:
$resource = fopen(‘/path/to/file.txt’, ‘r’);
// Открыть удаленный файл по протоколу HTTP:
// файл загружается по протоколу HTTP и доступен как локальный:
$resource = fopen(‘http://example.com/file.txt’, ‘r’);
// Дескриптор потока в общем виде:
$resource = fopen(‘{PROTOCOL}://{PATH}[?{QUERY}]’, ‘r’);
Потоковая Обвертка
class StreamWrapper
{
public function stream_open($path, $mode, $options, &$openedPath) {}
public function stream_close() {}
public function stream_eof() {}
public function stream_read($count) {}
public function stream_write($data) {}
}
stream_wrapper_register(‘foo’, StreamWrapper::class, STREAM_IS_URL);
Примеры
// Потоковая обвертка для протокола HTTP:
$resource = fopen(‘http://example.com/file.txt’, ‘r’);
// Потоковая обвертка для протокола FTP:
$resource = fopen(‘ftp://user:password@example.com/file.txt’, ‘r’);
// Потоковая обвертка для протокола SFTP:
$resource = fopen(‘ssh2.sftp://1234file.txt’, ‘r’);
// Потоковая обвертка для MongoDB GridFS:
$resource = fopen(‘gridfs://mydatabase.fs?filename=file.txt’, ‘r’);
// Потоковая обвертка для Amazon S3:
$resource = fopen(‘s3://bucket-name/file.txt’, ‘r’);
$bucket = Yii::$app->fileStorage->getBucket('tempFiles');
// открытие дескриптора потока:
$resource = $bucket->openFile(‘file.dat’, ‘r’);
while (!feof($resource)) {
echo fread($resource, 1024);
}
fclose($resource);
Обработка больших файлов
Максимальное количество файлов в
одном каталоге
• Для «старых» файловых систем (FAT) –
65 000
• Для современных (NTFS) теоретическое
– 4 294 967 295
• Практическое (без существенной потери
производительности) – 50 000..100 000
Шаблон под-каталога
return [
'components' => [
'fileStorage' => [
'class' => 'yii2tech\filestorage\local\Storage',
'buckets' => [
‘item' => [
'fileSubDirTemplate' => '{^name}/{^^name}',
],
]
// ...
];
$bucket = Yii::$app->fileStorage->getBucket(‘item');
$bucket->saveFileContent('foo.txt', 'Foo content');
// реальное имя файла - ‘f/o/foo.txt’
Связывание файлов с сущностями БД
Item
id
…
fileExtension
fileVersion
Имя файла
Сохранение
mime-типа
Чтобы
обмануть кэш
браузера
Расширение «yii2tech/ar-file»
"yii2tech/file-storage"
FileBehavior
“attached behavior”
Item
1
1file\Storage
file\Bucket
“consists of”
1
*
$fileAttribute
“read / write
file”"yii2tech/ar-file"
class Item extends \yii\db\ActiveRecord
{
public function behaviors()
{
return [
‘file’ => [
'class' => FileBehavior::class,
‘fileStorageBucket' => ‘item',
‘fileExtensionAttribute' => ‘fileExtension',
‘fileVersionAttribute' => ‘fileVersion',
]
];
}
}
Конфигурация «FileBehavior»
use yii\web\UploadedFile;
$model = Item::findOne(1);
$model->file = UploadedFile::getInstance($model, 'file');
$model->save();
var_dump($model->fileExists()); // выводит `true`
Сохранение файлов
{
“name”: “John Doe”,
“email”: “johndoe@example.com”,
“address” : {
“city”: “Houston”,
“region”: “Texas”,
},
“comments”: [
{
“date”: “2016-11-26 08:16:11”,
“content”: “Hello World”
},
…
]
}
Комплексные записи
Вложенные модели
User
$name
1“has address”
1
1
Address
$city
$region
Comment
$date
$content
mongodb\ActiveRecord
“has comments”
*
Model
Расширение «yii2tech/embedded»
"yii2tech/embedded"
User
1
ContainerTrait
*
Container
Interface
Mapping
Address
“Declare and
store”
Comment
“Satisfy
interface”
class User extends ActiveRecord implements ContainerInterface
{
use ContainerTrait;
public function embedAddressModel()
{
return $this->mapEmbedded(‘address', Address::class);
}
public function embedCommentModels()
{
return $this->mapEmbeddedList('comments', Comment::class);
}
}
Объявление вложенных объектов
$user = new User();
// Заполнение одиночного вложенного объекта:
$user->addressModel->city = ‘Houston';
$user->addressModel->region = ‘Texas';
// Заполнение списка:
$comment = new Comment();
$comment->content = ‘New comment’;
$user->comments[] = $comment;
// Синхронизация данных:
$user->refreshFromEmbedded();
var_dump($user->address); // выводит массив:
// ['city' => 'Houston', 'region' => 'Texas']
Доступ к вложенным объектам
События и поведения
Component
trigger()
attachBehavior()
$events
$behaviors
Behavior
events()
$owner
1*
Event
$sender
1 1
* *“Trigger” “Handle”
Handler
PHP
Callback
*
1
*
1
“Handle event” “Declare event handler”
1 *
“Has behavior”
“Has owner”
class User extends \yii\db\ActiveRecord
{
public function behaviors()
{
return [
‘timestamp’ => [
'class' => TimestampBehavior::class,
// обрабатывает только 2 собыития:
// - `beforeInsert`
// - `beforeUpdate`
]
];
}
}
Конфигурация «TimestampBehavior»
$user = new User(); // срабатывает событие `init`
// поведения проинициализированы!
$models = User::find()->all(); // срабатывает событие `afterFind`
// поведения проинициализированы!
unset($models); // объекты НЕ разрушены!
gc_collect_cycles(); // принудительная сборка мусора
// только теперь объекты разрушены
Инициализация и разрушение
поведений
class User extends \yii\db\ActiveRecord
{
public function init()
{
// «прыжок» через родителя
// убираем событие «init»
\yii\base\Model::init();
}
public function afterFind()
{
// нет вызова родительской реализации
// убираем событие «afterFind»
}
}
Устранение «лишних» событий
$user = new User(); // нет события `init`
// поведения НЕ проинициализированы!
$models = User::find()->all(); // нет события `afterFind`
// поведения НЕ проинициализированы!
unset($models); // объекты разрушены!
echo gc_collect_cycles(); // выводит `0`
$user->save(); // событие `beforeInsert`
// поведения проинициализированы!
Отложенная инициализация
поведений
Расширение «yii2tech/behavior-trait»
User BehaviorTrait
ActiveRecord
“Allow usage of
inline methods as
event handlers”
TimestampTrait
“Add particular event
handler”
class User extends \yii\db\ActiveRecord
{
use BehaviorTrait;
use TimestampTrait;
}
trait TimestampTrait
{
// Имя обработчика: «{событие}Handler{суффикс}»
// Обработка события «beforeInsert»
public function beforeInsertHandlerTimestamp($event)
{
$this->createdAt = time();
}
}
Использование «BehaviorTrait»
Примеры решения типичных задач за
рамками ядра Yii2
• Интернационализация в БД
• Роли в реляционных БД
• Абстракция файлового хранилища
• Связывание файлов с записями в БД
• Вложенные модели
• Trait вместо Behavior
http://www.yiiframework.com/
https://github.com/yii2tech