Примеры решения типичных задач за рамками ядра Yii2

44
Примеры решения типичных задач за рамками ядра Yii2 QuartSoft YiiSoft Климов П.В.

Transcript of Примеры решения типичных задач за рамками ядра Yii2

Page 1: Примеры решения типичных задач за рамками ядра Yii2

Примеры

решения

типичных задач

за рамками ядра

Yii2

QuartSoft

YiiSoft

Климов П.В.

Page 2: Примеры решения типичных задач за рамками ядра Yii2

Интернационализация статического

текста

I18N

$translations

translate()

“translation

source”

1

*MessageSource

translate()DbMessageSource

PhpMessageSource

Client

“Yii::t()”

Page 3: Примеры решения типичных задач за рамками ядра Yii2

Интернационализация сущностей в

базе данных

Itemn

id

canonicalName

price

Language

id

name

locale

Translation

n

itemId

languageId

name

description

Page 4: Примеры решения типичных задач за рамками ядра Yii2

Расширение «yii2tech/ar-variation»

VariationBehavior

$variationRelation

$defaultVariationRelation

“attached behavior”

1

*

Item

$canonicalName

Language

ItemTranslation

$name

1

1

1

* “has many”

“has many”

“has many via”

Page 5: Примеры решения типичных задач за рамками ядра Yii2

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»

Page 6: Примеры решения типичных задач за рамками ядра Yii2

$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']);

}

Управление вариаторами

Page 7: Примеры решения типичных задач за рамками ядра Yii2

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`

}

}

Вариация «по умолчанию»

Page 8: Примеры решения типичных задач за рамками ядра Yii2

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) {…}

}

Добавление виртуальных свойств

«хозяину» поведения

Page 9: Примеры решения типичных задач за рамками ядра Yii2

$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;

}

Прямой доступ к атрибутам вариатора

Page 10: Примеры решения типичных задач за рамками ядра Yii2

Сущности с общими атрибутами

Преподаватель Студент

- Ученая

степень

- Зарплата

- ФИО

- Паспорт

- Адрес

- Телефон

- Учебная

группа

- Стипендия

Page 11: Примеры решения типичных задач за рамками ядра Yii2

Роли в реляционной базе данных

Person

id

name

address

phone Student

personId

studyGroupId

scolarship

Instructor

personId

rankId

salary

Page 12: Примеры решения типичных задач за рамками ядра Yii2

Роли в реляционной базе данных

Person

1

id

name

address

phone Student

personId

studyGroupId

scolarship

Can be

1

Instructor

personId

rankId

salary

Can be1 1

Page 13: Примеры решения типичных задач за рамками ядра Yii2

Расширение «yii2tech/ar-role»

RoleBehavior

$roleRelation

“attached behavior”

1

Student

Instructor

Person

1

1

1“has one”

“has one”

1

1

“attached behavior”

1

1

Page 14: Примеры решения типичных задач за рамками ядра Yii2

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»

Page 15: Примеры решения типичных задач за рамками ядра Yii2

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);

}

}

Эмуляция наследования

Page 16: Примеры решения типичных задач за рамками ядра Yii2

$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;

}

Прямой доступ к атрибутам роли

Page 17: Примеры решения типичных задач за рамками ядра Yii2

Сохранение файлов

Client

Web

Server

HTTP

requests

HDD

File read/write

Page 18: Примеры решения типичных задач за рамками ядра Yii2

Распределенные приложения

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

Page 19: Примеры решения типичных задач за рамками ядра Yii2

Централизованное файловое хранилище

Client

Load

Balancer

Regular

HTTP

requests

Web

Server 1

Web

Server 2

Internal HTTP requests

File

ServerFTP / SFTP / REST

FTP / SFTP / REST

Page 20: Примеры решения типичных задач за рамками ядра Yii2

Абстракция файловой системы

sftp\Storage

file\Storage

$buckets

getBucket()

“consists of”

1

*file\Bucket

saveFileContent()

getFileContent()

local\Storage

sftp\Bucket

local\Bucket

Client

“file

r/w”

Page 21: Примеры решения типичных задач за рамками ядра Yii2

$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'

Операции с файлами

Page 22: Примеры решения типичных задач за рамками ядра Yii2

Файловые потоки

// Открыть локальный файл:

$resource = fopen(‘/path/to/file.txt’, ‘r’);

// Открыть удаленный файл по протоколу HTTP:

// файл загружается по протоколу HTTP и доступен как локальный:

$resource = fopen(‘http://example.com/file.txt’, ‘r’);

// Дескриптор потока в общем виде:

$resource = fopen(‘{PROTOCOL}://{PATH}[?{QUERY}]’, ‘r’);

Page 23: Примеры решения типичных задач за рамками ядра Yii2

Потоковая Обвертка

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);

Page 24: Примеры решения типичных задач за рамками ядра Yii2

Примеры

// Потоковая обвертка для протокола HTTP:

$resource = fopen(‘http://example.com/file.txt’, ‘r’);

// Потоковая обвертка для протокола FTP:

$resource = fopen(‘ftp://user:[email protected]/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’);

Page 25: Примеры решения типичных задач за рамками ядра Yii2

$bucket = Yii::$app->fileStorage->getBucket('tempFiles');

// открытие дескриптора потока:

$resource = $bucket->openFile(‘file.dat’, ‘r’);

while (!feof($resource)) {

echo fread($resource, 1024);

}

fclose($resource);

Обработка больших файлов

Page 26: Примеры решения типичных задач за рамками ядра Yii2

Максимальное количество файлов в

одном каталоге

• Для «старых» файловых систем (FAT) –

65 000

• Для современных (NTFS) теоретическое

– 4 294 967 295

• Практическое (без существенной потери

производительности) – 50 000..100 000

Page 27: Примеры решения типичных задач за рамками ядра Yii2

Шаблон под-каталога

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’

Page 28: Примеры решения типичных задач за рамками ядра Yii2

Связывание файлов с сущностями БД

Item

id

fileExtension

fileVersion

Имя файла

Сохранение

mime-типа

Чтобы

обмануть кэш

браузера

Page 29: Примеры решения типичных задач за рамками ядра Yii2

Расширение «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"

Page 30: Примеры решения типичных задач за рамками ядра Yii2

class Item extends \yii\db\ActiveRecord

{

public function behaviors()

{

return [

‘file’ => [

'class' => FileBehavior::class,

‘fileStorageBucket' => ‘item',

‘fileExtensionAttribute' => ‘fileExtension',

‘fileVersionAttribute' => ‘fileVersion',

]

];

}

}

Конфигурация «FileBehavior»

Page 31: Примеры решения типичных задач за рамками ядра Yii2

use yii\web\UploadedFile;

$model = Item::findOne(1);

$model->file = UploadedFile::getInstance($model, 'file');

$model->save();

var_dump($model->fileExists()); // выводит `true`

Сохранение файлов

Page 32: Примеры решения типичных задач за рамками ядра Yii2

{

“name”: “John Doe”,

“email”: “[email protected]”,

“address” : {

“city”: “Houston”,

“region”: “Texas”,

},

“comments”: [

{

“date”: “2016-11-26 08:16:11”,

“content”: “Hello World”

},

]

}

Комплексные записи

Page 33: Примеры решения типичных задач за рамками ядра Yii2

Вложенные модели

User

$name

$email

1“has address”

1

1

Address

$city

$region

Comment

$date

$content

mongodb\ActiveRecord

“has comments”

*

Model

Page 34: Примеры решения типичных задач за рамками ядра Yii2

Расширение «yii2tech/embedded»

"yii2tech/embedded"

User

1

ContainerTrait

*

Container

Interface

Mapping

Address

“Declare and

store”

Comment

“Satisfy

interface”

Page 35: Примеры решения типичных задач за рамками ядра Yii2

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);

}

}

Объявление вложенных объектов

Page 36: Примеры решения типичных задач за рамками ядра Yii2

$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']

Доступ к вложенным объектам

Page 37: Примеры решения типичных задач за рамками ядра Yii2

События и поведения

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”

Page 38: Примеры решения типичных задач за рамками ядра Yii2

class User extends \yii\db\ActiveRecord

{

public function behaviors()

{

return [

‘timestamp’ => [

'class' => TimestampBehavior::class,

// обрабатывает только 2 собыития:

// - `beforeInsert`

// - `beforeUpdate`

]

];

}

}

Конфигурация «TimestampBehavior»

Page 39: Примеры решения типичных задач за рамками ядра Yii2

$user = new User(); // срабатывает событие `init`

// поведения проинициализированы!

$models = User::find()->all(); // срабатывает событие `afterFind`

// поведения проинициализированы!

unset($models); // объекты НЕ разрушены!

gc_collect_cycles(); // принудительная сборка мусора

// только теперь объекты разрушены

Инициализация и разрушение

поведений

Page 40: Примеры решения типичных задач за рамками ядра Yii2

class User extends \yii\db\ActiveRecord

{

public function init()

{

// «прыжок» через родителя

// убираем событие «init»

\yii\base\Model::init();

}

public function afterFind()

{

// нет вызова родительской реализации

// убираем событие «afterFind»

}

}

Устранение «лишних» событий

Page 41: Примеры решения типичных задач за рамками ядра Yii2

$user = new User(); // нет события `init`

// поведения НЕ проинициализированы!

$models = User::find()->all(); // нет события `afterFind`

// поведения НЕ проинициализированы!

unset($models); // объекты разрушены!

echo gc_collect_cycles(); // выводит `0`

$user->save(); // событие `beforeInsert`

// поведения проинициализированы!

Отложенная инициализация

поведений

Page 42: Примеры решения типичных задач за рамками ядра Yii2

Расширение «yii2tech/behavior-trait»

User BehaviorTrait

ActiveRecord

“Allow usage of

inline methods as

event handlers”

TimestampTrait

“Add particular event

handler”

Page 43: Примеры решения типичных задач за рамками ядра Yii2

class User extends \yii\db\ActiveRecord

{

use BehaviorTrait;

use TimestampTrait;

}

trait TimestampTrait

{

// Имя обработчика: «{событие}Handler{суффикс}»

// Обработка события «beforeInsert»

public function beforeInsertHandlerTimestamp($event)

{

$this->createdAt = time();

}

}

Использование «BehaviorTrait»

Page 44: Примеры решения типичных задач за рамками ядра Yii2

Примеры решения типичных задач за

рамками ядра Yii2

• Интернационализация в БД

• Роли в реляционных БД

• Абстракция файлового хранилища

• Связывание файлов с записями в БД

• Вложенные модели

• Trait вместо Behavior

http://www.yiiframework.com/

https://github.com/yii2tech