Продвинутое использование ActiveRecord в Yii2

42
Продвинутое использование ActiveRecord в Yii2 QuartSoft YiiSoft Климов П.В.

Transcript of Продвинутое использование ActiveRecord в Yii2

Page 1: Продвинутое использование ActiveRecord в Yii2

Продвинутое

использование

ActiveRecord в

Yii2

QuartSoft

YiiSoft

Климов П.В.

Page 2: Продвинутое использование ActiveRecord в Yii2

Мульти-СУБД Приложения

Client

Web

Application

MySQL MongoDB

HTML

HTTP

request

SQL

Mongo

Query

Implicit

relation

Page 3: Продвинутое использование ActiveRecord в Yii2

MongoDBMySQL

User1

id

username

email

BlogPost

_id

authorId

date

content

Create

n

Структура БД

Page 4: Продвинутое использование ActiveRecord в Yii2

MySQLMongoDB

Отображение

Page 5: Продвинутое использование ActiveRecord в Yii2

Выборка «по частям»

SELECT id, postDate, content, authorId

FROM BlogPost

WHERE 1

LIMIT 10, 10

SELECT id, username

FROM User

WHERE id IN (10, 11, 15, 18)

/* authorId = [10, 11, 15, 18] */

Page 6: Продвинутое использование ActiveRecord в Yii2

Active Record

ActiveRecord

BaseActiveRecord

find()

save()

Command

“Instantiate by

query result”

“Database

access”

1*

1

*Query

ActiveQuery

ActiveRelationTrait

ActiveRecord

Interface

ActiveQuery

Interface

“Database

access” 1*

Page 7: Продвинутое использование ActiveRecord в Yii2

ActiveRecord

BlogPost

find()

“Instantiate by

query result”

1*ActiveQuery

User

find()

UserQuery

BlogPostQuery

“Search

specification”

Page 8: Продвинутое использование ActiveRecord в Yii2

Отношение – ActiveQuery +

связующее условие

BlogPost

getUser()

User

getBlogPosts()

UserQueryBlogPostQuery

“has one”

“has many”

Page 9: Продвинутое использование ActiveRecord в Yii2

class BlogPost extends \yii\db\ActiveRecord

{

public function getAuthor()

{

return $this->hasOne(User::class, [‘id’ => ‘authorId’]);

}

}

// Отложенная загрузка отношения:

$user = BlogPost::find()->where([‘id’ => 12])->one()->author;

// Жадная загрузка отношения:

$posts = BlogPost::find()->with([‘author’])->all();

Page 10: Продвинутое использование ActiveRecord в Yii2

«Жадная» загрузка отношений:ActiveQuery

:BlogPost

Client

‘find with

"profile"’

‘result’

‘create from

query result’

‘get "user"

relation’

:User

:ActiveQuery

‘relation info’

‘find where userId in set’

‘user references’

‘bind "user"’

Page 11: Продвинутое использование ActiveRecord в Yii2

NOSQL Active Record

yii2-elasticsearch

yii2-mongodb

yii2-redis

yii2-sphinx

Page 12: Продвинутое использование ActiveRecord в Yii2

MongoDB ActiveRecord

mongodb\ActiveRecord

BaseActiveRecord

mongodb\Collection1

*

1

mongodb\Query

mongodb\ActiveQuery

ActiveRelationTrait

ActiveRecord

Interface

ActiveQuery

Interface

1

11

Page 13: Продвинутое использование ActiveRecord в Yii2

Отношение «многие ко многим» в

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

Tagn

id

name

rating

BlogPost

id

authorId

date

content

Has tagsm

blogPostId

tagId

Page 14: Продвинутое использование ActiveRecord в Yii2

Отношение «многие ко многим» в

MongoDB

Tagn

id

name

rating

BlogPost

id

authorId

date

content

tagIds

Has tagsm

{

“authorId”: 96,

“date”: “2015-08-10”,

“content”: “some content”,

“tagIds”: [10, 5, 8, 11]

}

Page 15: Продвинутое использование ActiveRecord в Yii2

class BlogPost extends \yii\mongodb\ActiveRecord

{

public function getTags()

{

return $this->hasMany(Tag::class, [‘id’ => ‘tagIds’]);

}

}

$posts = BlogPost::find()->with([‘tags’])->all();

foreach ($posts as $post) {

var_dump($post->tags); // выводит “Tag[ ]”

}

Page 16: Продвинутое использование ActiveRecord в Yii2

Системы полнотекстового поиска

SphinxMySQL

BlogPost1

BlogPostIndex

id

authorId

date

Has

full-text

index

1

_id

authorId

date

content

Page 17: Продвинутое использование ActiveRecord в Yii2

Sphinx Snippet

SELECT *

FROM BlogPostIndex

WHERE MATCH(‘Yii’)

CALL SNIPPETS(

(

'Yii 2.0.6 is released',

‘Yii 2.0.5 is released (security fix)‘

),

BlogPostIndex,

‘Yii’);

1)

2)

Page 18: Продвинутое использование ActiveRecord в Yii2

class BlogPostIndex extends \yii\sphinx\ActiveRecord

{

public function getSource()

{

return $this->hasOne(BlogPost::class, [‘id’ => ‘id’]);

}

public function getSnippetSource()

{

return $this->source->content;

}

}

// Find with snippets :

$posts = BlogPostIndex::find()

->with([‘source’])

->match(‘Yii’)

->snippetByModel()

->all();

Page 19: Продвинутое использование ActiveRecord в Yii2

/* SphinxQL */

SELECT *

FROM BlogPostIndex

WHERE MATCH(‘Yii’)

/* SphinxQL */

CALL SNIPPETS((/* source list */), BlogPostIndex, ‘Yii’);

1)

2) /* MySQL */

SELECT *

FROM BlogPost

WHERE id IN (/* id list */)

3)

Page 20: Продвинутое использование ActiveRecord в Yii2

Статические данные

User

1

id

username

email

statusId

UserStatus

id

name

Has

Status

n

- Active

- Cancelled

- Pending

Page 21: Продвинутое использование ActiveRecord в Yii2

class UserStatus extends \yii2tech\filedb\ActiveRecord

{

public function fileName()

{

return ‘user_status';

}

}

// File ‘user_status.php’ :

return [

1 => ['name' => ‘Active'],

2 => ['name' => ‘Cancelled'],

3 => ['name' => ‘Pending'],

];

$users = User::find()->with(‘status’)->all();

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

Page 22: Продвинутое использование ActiveRecord в Yii2

Работа с web-сервисами

Client

“End-user”

Web

Application

Regular

HTTP

requests

REST

API

“User”

Web Service

“Payment”

Web Service

REST

API

REST

API

Page 23: Продвинутое использование ActiveRecord в Yii2

class User extends \rest\ActiveRecord

{

public function endpoint()

{

return ‘http://user.domain.com/1.0/users';

}

}

class Payment extends \rest\ActiveRecord

{

public function endpoint()

{

return ‘http://payment.domain.com/1.0/payments';

}

}

$users = User::find()->with(‘payment’)->all();

Page 24: Продвинутое использование ActiveRecord в Yii2

Фильтрация по данным

отношения

MongoDB MySQL

Page 25: Продвинутое использование ActiveRecord в Yii2

Уточняющий запрос фильтрации

SELECT id

FROM User

WHERE username LIKE ‘%samdark%’

SELECT *

FROM BlogPost

WHERE authorId IN (1, 15)

LIMIT 10, 10

SELECT id, username

FROM User

WHERE id IN (1, 15)

1)

2)

3)

Page 26: Продвинутое использование ActiveRecord в Yii2

// Уточнение фильтра :

$userIds = User::find()

->select([‘id’])

->where([‘like’, ‘username’, ‘samdark’])

->column();

// Выборка с фильтрацией:

$posts = BlogPost::find()

->with([‘author’])

->where([‘in’, ‘authorId’, $userIds])

->all();

Page 27: Продвинутое использование ActiveRecord в Yii2

Наличие отношения – как

условие выборки

User Agreement

id username id date

1 John 1 2015-05-11

2 Mark - -

3 Michael 2 2015-06-09

4 Tomas - -

Agreement1

id

userId

date

paymentInfo

notes

User

id

username

email

password

Sign up1

Page 28: Продвинутое использование ActiveRecord в Yii2

SELECT u.username, u.email, a.date, a.paymentInfo

FROM User AS u

INNER JOIN Agreement AS a ON u.id = a.userId

WHERE 1

// Типичная ошибка: User + Agreement

$result = User::find()

->with([‘agreement’])

// Как добавить условие обязательного наличия

// связанных записей?

->all();

SQL

Yii Active Record

Page 29: Продвинутое использование ActiveRecord в Yii2

Инверсия отношения

// Построение выборки в обратном порядке:

// Agreement + User

$result = Agreement::find()

->with([‘user’])

->all();

User Agreement

id username id date

1 John 1 2015-05-11

3 Michael 2 2015-06-09

Page 30: Продвинутое использование ActiveRecord в Yii2

Агрегация

User1

id

username

email

Payment

id

userId

date

amount

Pay

n

User Payment

id username SUM(amount)

1 John 150

3 Michael 270

Page 31: Продвинутое использование ActiveRecord в Yii2

SELECT u.*, SUM(p.amount)

FROM User AS u

INNER JOIN Payment AS p ON u.id = p.userId

GROUP BY u.id

$result = User::find()

->with([‘payment’ => function ($query) {

// Как добавить агрегацию привязанную у User?

}])

->all();

SQL

Yii Active Record

Page 32: Продвинутое использование ActiveRecord в Yii2

SELECT u.*, p.amountSum

FROM User AS u

INNER JOIN (

SELECT userId, SUM(amount) AS amountSum

FROM Payment

GROUP BY p.userId

) AS p ON u.id = p.userId

Альтернативный SQL

Page 33: Продвинутое использование ActiveRecord в Yii2

class User extends \yii\db\ActiveRecord

{

public function getPayments()

{

return $this->hasMany(Payment::class, [‘userId' => 'id']);

}

public function getPaymentAggregation()

{

return $this->getPayments()

->select([‘userId', ‘amountSum' => ‘SUM(amount)'])

->groupBy(‘userId')

->asArray(true);

}

public function getAmountSum()

{

return $this->paymentAggregations[0][‘amountSum’];

}

}

Page 34: Продвинутое использование ActiveRecord в Yii2

$users = User::find()

->with([‘paymentAggregation’]) // «жадная» загрузка

->all();

// результат агрегации по отношению:

echo $users[0]->amountSum;

// отложенная загрузка:

$user = User::findOne($id);

echo $user->amountSum; // дополнительный запрос

Page 35: Продвинутое использование ActiveRecord в Yii2

$posts = BlogPost::find()

->joinWith([‘author’])

->orderBy([

‘User.registeredAt’ => SORT_DESC,

‘BlogPost.createdAt’ => SORT_DESC,

])

->all();

// Оператор `JOIN` применяется на уровне `BlogPostQuery`,// но не отменяет запроса на «жадную» загрузка отношения:

Реляционная выборка и объединение

(JOIN) таблиц

1) SELECT *

FROM BlogPost

LEFT JOIN User ON User.id = BlogPost.authorId

ORDER BY User.registeredAt DESC

2) SELECT *

FROM User

WHERE id IN (/* id list */)

Page 36: Продвинутое использование ActiveRecord в Yii2

$records = BlogPost::find()

->select([

‘BlogPost.*’,

‘User.name AS authorName’,

‘User.registeredAt AS authorRegisteredAt’)

->joinWith([‘author’], false) // отключаем «жадную» загрузку

->asArray(true) // выводим результат в массив

->all();

print_r($records[0]);// выводит:

[

‘id’ => ’15’,

‘content’ => ‘Advanced ActiveRecord usage…’,

‘authorName’ => ‘John Doe’,

‘authorRegisteredAt’ => ‘2016-08-12’,

]

Использование `asArray()`

Page 37: Продвинутое использование ActiveRecord в Yii2

class BlogPost extends \yii\db\ActiveRecord

{

public $authorName;

private $_authorRegisteredAt;

public function setAuthorRegisteredAt($value)

{

$this->_authorRegisteredAt = $value;

}

public function getAuthorRegisteredAt()

{

return $this->_authorRegisteredAt ?: $this->author->registeredAt;

}

}

Добавление полей для результатов

запроса

Page 38: Продвинутое использование ActiveRecord в Yii2

$post = BlogPost::findOne($someId);

echo $post->authorName; // выводит `null`!

echo $post->authorRegisteredAt; // OK

$posts = BlogPost::find()

->select([

‘BlogPost.*’,

‘User.name AS authorName’,

‘User.registeredAt AS authorRegisteredAt’

])

->joinWith([‘author’], false) // отключаем «жадную» загрузку

->all();

echo $posts[0]->authorName; // OK

echo $posts[0]->authorRegisteredAt; // OK

echo $posts[0]->author->name; // дополнительный запрос!

Page 39: Продвинутое использование ActiveRecord в Yii2

class BlogPost extends \yii\db\ActiveRecord

{

public static function populateRecord($record, $row)

{

foreach ($row as $name => $value) { // проверяем есть ли

if (strpos($name, ‘author’) === 0) { // поля для отношения

$authorRow[substr($name, 6)] = $value;

unset($row[$name]);

}

}

$record = parent::populateRecord($record, $row);

if (!empty($authorRow)) {

$author = new User(); // создаем и

$author->attributes = $authorRow; // заполняем отношение

$record->populateRelation(‘author’, $author); // вручную

}

return $record;

}

}

Заполнение отношения вручную

Page 40: Продвинутое использование ActiveRecord в Yii2

$posts = BlogPost::find()

->select([

‘BlogPost.*’,

‘User.name AS authorName’,

‘User.registeredAt AS authorRegisteredAt’

])

->joinWith([‘author’], false) // отключаем «жадную» загрузку

->all();

echo $posts[0]->author->name; // нет дополнительного запроса!

Page 41: Продвинутое использование ActiveRecord в Yii2

class BlogPost extends \yii\db\ActiveRecord

{

use \yii2tech\ar\eagerjoin\EagerJoinTrait;

public static function find()

{

return new BlogPostQuery(get_called_class());

}

}

class BlogPostQuery extends \yii\db\ActiveQuery

{

use \yii2tech\ar\eagerjoin\EagerJoinQueryTrait;

}

$records = BlogPost::find()->eagerJoinWith(‘author')->all();

$records[0]->author->name; // нет дополнительного запроса!

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

Page 42: Продвинутое использование ActiveRecord в Yii2

Продвинутое использование

ActiveRecord в Yii2

• Выборка «по частям»

• ActiveRecord отношения и ActiveQuery

• Дополнительный возможности NOSQL отношений

• Статические данные

• Web-сервисы

• Фильтрация по связанным данным

• Агрегация данных

• Объединение таблиц

http://www.yiiframework.com/

https://github.com/yii2tech/filedb

https://github.com/yii2tech/ar-eagerjoin