mcaskill / charcoal-model-collection
为Charcoal提供的模型集合和仓库类。
Requires
- php: >7.1
- locomotivemtl/charcoal-cache: ~0.2
- locomotivemtl/charcoal-core: ~0.6
Requires (Dev)
- php-coveralls/php-coveralls: ^2.2
- phpunit/phpunit: ^7.1
- psr/log: ^1.1
- squizlabs/php_codesniffer: ^3.5
README
为Charcoal项目提供高级模型集合和集合加载器的支持包。
安装
composer require mcaskill/charcoal-model-collection
查看composer.json
以获取依赖项。
集合
1. Charcoal\Support\Model\Collection\Collection
提供操作集合或检索特定模型的方法。
filter()
使用给定的回调过滤对象集合。
$collection = new Collection([ $a, $b, $c, $d, $e ]); // [ // 1 => Model (active: 1), 2 => Model (active: 0), // 3 => Model (active: 1), 4 => Model (active: 1), // 5 => Model (active: 0) // ] $filtered = $collection->filter(function ($obj, $id) { return ($obj['active'] === true); }); // [ 1 => Model, 3 => Model, 4 => Model ]
forPage()
通过将集合切分成更小的集合来“分页”集合。
$collection = new Collection([ $a, $b, $c, $d, $e, $f, $g, $h, $i, $j ]); // [ 1 => Model, 2 => Model, 3 => Model, 4 => Model, 5 => Model,… ] $chunk = $collection->forPage(2, 3); // [ 4 => Model, 5 => Model, 6 => Model ]
only()
提取具有指定键的对象。
$collection = new Collection([ $a, $b, $c, $d, $e ]); // [ 1 => Model, 2 => Model, 3 => Model, 4 => Model, 5 => Model ] $filtered = $collection->only(2); // [ 2 => Model ] $filtered = $collection->only([ 1, 3 ]); // [ 1 => Model, 3 => Model ] $filtered = $collection->only(2, 4); // [ 2 => Model, 4 => Model ]
pop()
从集合中移除并返回最后一个对象。
$collection = new Collection([ $a, $b, $c, $d, $e ]); // [ 1 => Model, 2 => Model, 3 => Model, 4 => Model, 5 => Model ] $collection->pop(); // Model (5) $collection->toArray(); // [ 1 => Model, 2 => Model, 3 => Model, 4 => Model ]
prepend()
将对象添加到集合的开始处。
$collection = new Collection([ $a, $b, $c, $d, $e ]); // [ 1 => Model, 2 => Model, 3 => Model, 4 => Model, 5 => Model ] $collection->prepend($o); // Model (15) $filtered->toArray(); // [ 15 => Model, 1 => Model, 2 => Model, 3 => Model, 4 => Model, 5 => Model ]
random()
从集合中检索一个或多个随机对象。
$collection = new Collection([ $a, $b, $c, $d, $e ]); // [ 1 => Model, 2 => Model, 3 => Model, 4 => Model, 5 => Model ] $collection->random(); // Model (3) $collection->random(2); // [ 1 => Model, 3 => Model ]
reverse()
反转集合中对象的顺序。
$collection = new Collection([ $a, $b, $c, $d, $e ]); // [ 1 => Model, 2 => Model, 3 => Model, 4 => Model, 5 => Model ] $reversed = $collection->reverse(); // [ 5 => Model, 4 => Model, 3 => Model, 2 => Model, 1 => Model ]
shift()
从集合中移除并返回第一个对象。
$collection = new Collection([ $a, $b, $c, $d, $e ]); // [ 1 => Model, 2 => Model, 3 => Model, 4 => Model, 5 => Model ] $collection->shift(); // Model (1) $collection->toArray(); // [ 2 => Model, 3 => Model, 4 => Model, 5 => Model ]
slice()
提取集合的切片。
$collection = new Collection([ $a, $b, $c, $d, $e, $f, $g, $h, $i, $j ]); // [ 1 => Model, 2 => Model, 3 => Model, 4 => Model, 5 => Model,… ] $slice = $collection->slice(4); // [ 5 => Model, 6 => Model, 7 => Model, 8 => Model, 9 => Model, 10 => Model ] $slice = $collection->slice(4, 2); // [ 5 => Model, 6 => Model ]
sortBy()
按给定回调或对象属性对集合进行排序。
$collection = new Collection([ $a, $b, $c ]); // [ 1 => Model (position: 5), 2 => Model (position: 2), 3 => Model (position: 0) ] $sorted = $collection->sortBy('position'); // [ 3 => Model, 2 => Model, 1 => Model ]
sortByDesc()
使用给定回调或对象属性按降序对集合进行排序。
take()
从集合的第一或最后一个对象中提取部分内容。
$collection = new Collection([ $a, $b, $c, $d, $e, $f ]); // [ 1 => Model, 2 => Model, 3 => Model, 4 => Model, 5 => Model, 6 => Model ] $chunk = $collection->take(3); // [ 1 => Model, 2 => Model, 3 => Model ] $chunk = $collection->take(-2); // [ 5 => Model, 6 => Model ]
where()
根据给定的键/值对过滤对象集合。
$collection = new Collection([ $a, $b, $c, $d, $e ]); // [ // 1 => Model (active: 1), 2 => Model (active: 0), // 3 => Model (active: 1), 4 => Model (active: 1), // 5 => Model (active: 0) // ] $filtered = $collection->where('active', true); // [ 1 => Model, 3 => Model, 4 => Model ]
whereIn()
根据给定的键/值对过滤对象集合。
$collection = new Collection([ $a, $b, $c, $d, $e ]); // [ // 1 => Model (name: "Lorem"), 2 => Model (name: "Ipsum"), // 3 => Model (name: "Dolor"), 4 => Model (name: "Elit"), // 5 => Model (name: "Amet") // ] $filtered = $collection->whereIn('name', [ 'Amet', 'Dolor' ]); // [ 3 => Model, 5 => Model ]
仓库
1. Charcoal\Support\Model\Repository\CollectionLoaderIterator
提供改进的行数计数(通过 SQL_CALC_FOUND_ROWS
),支持PHP生成器通过“光标”方法,并支持将加载器直接链接到迭代器构造或附加额外条件。
1.1. 懒集合
CollectionLoaderIterator
利用PHP的生成器,允许您在保持内存使用低的同时处理大型集合。
使用传统的 load
方法时,必须同时将所有模型加载到内存中。
$repository = (new CollectionLoaderIterator)->setModel(Post::class); $repository->addFilter('active', true)->addFilter('published <= NOW()'); $posts = $repository->load(); // query: SELECT … // array: Post, Post, Post,… foreach ($posts as $post) { echo $post['title']; }
然而,cursor
方法返回 Generator
对象。这允许您一次只将一个模型加载到内存中
$repository = (new CollectionLoaderIterator)->setModel(Post::class); $repository->addFilter('active', true)->addFilter('published <= NOW()'); $posts = $repository->cursor(); // Generator foreach ($posts as $post) { // query: SELECT … // Post echo $post['title']; }
1.2. IteratorAggregate
CollectionLoaderIterator
实现IteratorAggregate
,允许仓库在foreach
构造中使用,无需显式调用查询方法。
class Post extends AbstractModel { /** * @return Comment[]|CollectionLoaderIterator */ public function getComments() : iterable { $comments = (new CollectionLoaderIterator)->setModel(Comment::class); $byPost = [ 'property' => 'post_id', 'value' => $this['id'], ]; return $comments->addFilter($byPost); } }
内部,IteratorAggregate::getIterator()
方法调用 CollectionLoaderIterator::cursor()
方法,后者返回一个 Generator
对象。
$post = $factory->create(Post::class)->load(1); foreach ($post['comments'] as $comment) { // query: SELECT … // Comment }
此外,您还可以继续将约束附加到仓库
$post = $factory->create(Post::class)->load(1); $comments = $post['comments']->addFilter('approved', true); // CollectionLoaderIterator foreach ($comments as $comment) { // query: SELECT … // Comment }
1.3. SQL_CALC_FOUND_ROWS
如果将SQL_CALC_FOUND_ROWS
选项包含在SELECT
语句中,则在之后将调用FOUND_ROWS()
函数来检索该语句在没有LIMIT
时将返回的对象数量。
使用查询构建器接口,生成的语句将包含SQL_CALC_FOUND_ROWS
选项,除非查询针对单个对象。
$repository = (new CollectionLoaderIterator)->setModel(Post::class); $repository->addFilter('active', true) ->addFilter('published <= NOW()') ->setNumPerPage(10) ->setPage(3); // Automatically find total count from query builder $posts = $repository->load(); // query: SELECT SQL_CALC_FOUND_ROWS * FROM `charcoal_users` WHERE ((`active` = '1') AND (`published` <= NOW())) LIMIT 30, 10; // query: SELECT FOUND_ROWS(); $total = $repository->foundObjs(); // int: 38 // Automatically find total count from query $users = $repository->reset()->loadFromQuery('SELECT SQL_CALC_FOUND_ROWS * … LIMIT 0, 20'); // query: SELECT SQL_CALC_FOUND_ROWS * … LIMIT 0, 20; // query: SELECT FOUND_ROWS(); $total = $repository->foundObjs(); // int: 38 // Automatically find total count from query $users = $repository->reset()->loadFromQuery('SELECT * … LIMIT 0, 20'); // query: SELECT * … LIMIT 0, 20; $total = $repository->foundObjs(); // LogicException: Can not count found objects for the last query
2. Charcoal\Support\Model\Repository\ModelCollectionLoader
提供克隆、防止模型交换和共享相同的 数据源 的支持。
2.1. 模型保护
一旦模型被分配到 ModelCollectionLoader
,任何替换它的尝试都会引发异常。
$repository = (new ModelCollectionLoader)->setModel(Post::class); // … $repository->setModel(Comment::class); // RuntimeException: A model is already assigned to this collection loader: \App\Model\Post
单独来看,这个特性并不实用,但与 ScopedCollectionLoader
结合使用,则成为一项重要的安全措施。
2.2. 集合加载器克隆
通过 clone
关键字或 cloneWith()
方法克隆 ModelCollectionLoader
时,模型保护机制将暂时解除,直到分配新的对象类型或调用 source()
方法。
$postsLoader = (new ModelCollectionLoader)->setModel(Post::class); $commentsLoader = (clone $postsLoader)->setModel(Comment::class);
$postsLoader = (new ModelCollectionLoader)->setModel(Post::class); $commentsLoader = $postsLoader->cloneWith(Comment::class); $tagsLoader = $postsLoader->cloneWith([ 'model' => Tag::class, 'collection' => 'array', ]);
2.3. 源共享
Charcoal 模型基于 ActiveRecord 实现的数据源操作;也就是说,模型允许您与数据库中的数据进行交互。这种交互通过“数据源”接口实现,例如 DatabaseSource
类。每个模型的实例通常都会创建自己的数据源对象实例;换句话说,您会为每个模型(模型和数据源)处理两个对象。
为了减少请求生命周期中的对象数量,将单个数据源实例分配给所有模型是一种良好的做法。当 ModelCollectionLoader
创建要查询的模型的新实例时,它将分配原型模型的数据源对象(仓库查询的那个对象)。
$posts = (new BaseCollectionLoader)->setModel(Post::class)->load(); // array: Post, Post, Post,… ($posts[0]->source() === $posts[2]->source()) // bool: false $posts = (new ModelCollectionLoader)->setModel(Post::class)->load(); // array: Post, Post, Post,… ($posts[0]->source() === $posts[2]->source()) // bool: true
3. Charcoal\Support\Model\Repository\ScopedCollectionLoader
提供默认过滤器、排序和分页的支持,这些在创建加载器后和每次重置后自动应用。
$repository = new ScopedCollectionLoader([ 'logger' => $container['logger'], 'factory' => $container['model/factory'], 'model' => Post::class, 'default_filters' => [ [ 'property' => 'active', 'value' => true, ], [ 'property' => 'publish_date', 'operator' => 'IS NOT NULL', ], ], 'default_orders' => [ [ 'property' => 'publish_date', 'direction' => 'desc', ], ], 'default_pagination' => [ 'num_per_page' => 20, ], ]); $posts = $repository->addFilter('publish_date <= NOW()')->load(); // query: SELECT SQL_CALC_FOUND_ROWS * FROM `posts` WHERE ((`active` = '1') AND (`publish_date` IS NOT NULL) AND (`published` <= NOW())) ORDER BY `publish_date` DESC LIMIT 20; $repository->reset()->load(); // query: SELECT SQL_CALC_FOUND_ROWS * FROM `posts` WHERE ((`active` = '1') AND (`publish_date` IS NOT NULL)) ORDER BY `publish_date` DESC LIMIT 20;
如果您想禁用仓库上的默认条件,可以使用 withoutDefaults
方法。该方法接受一个回调,用于与集合加载器交互,例如,如果您只想应用默认排序。
$repository = new ScopedCollectionLoader([…]); $posts = $repository->withoutDefaults(function () { $this->applyDefaultOrders(); $this->applyDefaultPagination(); })->load(); // query: SELECT SQL_CALC_FOUND_ROWS * FROM `posts` ORDER BY `publish_date` DESC LIMIT 20;
4. Charcoal\Support\Model\Repository\CachedCollectionLoader
提供将加载模型的存储在 缓存池 中的支持,类似于 \Charcoal\Model\Service\ModelLoader
,并使用相同的缓存键进行互操作性。
$repository = new CachedCollectionLoader([ 'cache' => $container['cache'], 'logger' => $container['logger'], 'factory' => $container['model/factory'], 'model' => Post::class, ]);
如果您想禁用仓库上的缓存过程,可以使用 withoutCache
方法。该方法接受一个回调,用于与集合加载器交互。
$repository = new CachedCollectionLoader([…]); $posts = $repository->withoutCache()->cursor(); // Generator