Upload
paul-klimov
View
335
Download
13
Embed Size (px)
Citation preview
Продвинутое
использование
ActiveRecord в
Yii2
QuartSoft
YiiSoft
Климов П.В.
Мульти-СУБД Приложения
Client
Web
Application
MySQL MongoDB
HTML
HTTP
request
SQL
Mongo
Query
Implicit
relation
MongoDBMySQL
User1
id
username
BlogPost
_id
authorId
date
content
Create
n
Структура БД
MySQLMongoDB
Отображение
Выборка «по частям»
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] */
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*
ActiveRecord
BlogPost
find()
“Instantiate by
query result”
1*ActiveQuery
User
find()
UserQuery
BlogPostQuery
“Search
specification”
Отношение – ActiveQuery +
связующее условие
BlogPost
getUser()
User
getBlogPosts()
UserQueryBlogPostQuery
“has one”
“has many”
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();
«Жадная» загрузка отношений: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"’
NOSQL Active Record
yii2-elasticsearch
yii2-mongodb
yii2-redis
yii2-sphinx
MongoDB ActiveRecord
mongodb\ActiveRecord
BaseActiveRecord
mongodb\Collection1
*
1
mongodb\Query
mongodb\ActiveQuery
ActiveRelationTrait
ActiveRecord
Interface
ActiveQuery
Interface
1
11
Отношение «многие ко многим» в
реляционной базе данных
Tagn
id
name
rating
BlogPost
id
authorId
date
content
Has tagsm
blogPostId
tagId
Отношение «многие ко многим» в
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]
}
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[ ]”
}
Системы полнотекстового поиска
SphinxMySQL
BlogPost1
BlogPostIndex
id
authorId
date
Has
full-text
index
1
_id
authorId
date
content
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)
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();
/* 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)
Статические данные
User
1
id
username
statusId
UserStatus
id
name
Has
Status
n
- Active
- Cancelled
- Pending
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»
Работа с web-сервисами
Client
“End-user”
Web
Application
Regular
HTTP
requests
REST
API
“User”
Web Service
“Payment”
Web Service
REST
API
REST
API
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();
Фильтрация по данным
отношения
MongoDB MySQL
Уточняющий запрос фильтрации
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)
// Уточнение фильтра :
$userIds = User::find()
->select([‘id’])
->where([‘like’, ‘username’, ‘samdark’])
->column();
// Выборка с фильтрацией:
$posts = BlogPost::find()
->with([‘author’])
->where([‘in’, ‘authorId’, $userIds])
->all();
Наличие отношения – как
условие выборки
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
password
Sign up1
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
Инверсия отношения
// Построение выборки в обратном порядке:
// 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
Агрегация
User1
id
username
Payment
id
userId
date
amount
Pay
n
User Payment
id username SUM(amount)
1 John 150
3 Michael 270
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
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
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’];
}
}
$users = User::find()
->with([‘paymentAggregation’]) // «жадная» загрузка
->all();
// результат агрегации по отношению:
echo $users[0]->amountSum;
// отложенная загрузка:
$user = User::findOne($id);
echo $user->amountSum; // дополнительный запрос
$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 */)
$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()`
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;
}
}
Добавление полей для результатов
запроса
$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; // дополнительный запрос!
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;
}
}
Заполнение отношения вручную
$posts = BlogPost::find()
->select([
‘BlogPost.*’,
‘User.name AS authorName’,
‘User.registeredAt AS authorRegisteredAt’
])
->joinWith([‘author’], false) // отключаем «жадную» загрузку
->all();
echo $posts[0]->author->name; // нет дополнительного запроса!
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»
Продвинутое использование
ActiveRecord в Yii2
• Выборка «по частям»
• ActiveRecord отношения и ActiveQuery
• Дополнительный возможности NOSQL отношений
• Статические данные
• Web-сервисы
• Фильтрация по связанным данным
• Агрегация данных
• Объединение таблиц
http://www.yiiframework.com/
https://github.com/yii2tech/filedb
https://github.com/yii2tech/ar-eagerjoin