mcaskill/charcoal-model-collection

为Charcoal提供的模型集合和仓库类。

1.4.0 2020-09-21 13:09 UTC

This package is auto-updated.

Last update: 2024-09-18 16:27:37 UTC


README

License Latest Stable Version Build Status

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

许可协议

  • Charcoal Model Collections and Repositories 组件采用 MIT 许可协议。有关详细信息,请参阅 LICENSE
  • Charcoal 框架采用 MIT 许可协议。有关详细信息,请参阅 LICENSE