allcaretravel/laravel-categories

Rinvex Categories 是一个用于分类管理的多态 Laravel 扩展包。您可以使用它轻松地对任何 Eloquent 模型进行分类,并利用嵌套集合、Sluggable 和可翻译模型的力量。


README

Rinvex Categories 是一个用于分类管理的多态 Laravel 扩展包。您可以使用它轻松地对任何 Eloquent 模型进行分类,并利用 嵌套集合Sluggable可翻译 模型的力量。

Packagist Scrutinizer Code Quality Travis StyleCI License

安装

  1. 通过 composer 安装该包

    composer require rinvex/laravel-categories
  2. 发布资源(迁移和配置文件)

    php artisan rinvex:publish:categories
  3. 执行以下命令以执行迁移

    php artisan rinvex:migrate:categories
  4. 完成!

使用方法

要为您的 Eloquent 模型添加分类支持,只需使用 \Rinvex\Categories\Traits\Categorizable 特性。

管理您的分类

您的分类只是普通的 Eloquent 模型,因此您可以像处理它们一样处理它们。这里没有特别之处!

注意:由于 Rinvex Categories 扩展并利用了其他优秀的包,因此请查阅以下文档以获取更多详细信息:

管理您的可分类模型

API 界面直观且非常直接,让我们快速浏览一下。

// Get all categories
$allCategories = app('rinvex.categories.category')->first();

// Get instance of your model
$post = new \App\Models\Post::find();

// Get attached categories collection
$post->categories;

// Get attached categories query builder
$post->categories();

您可以通过多种方式附加分类

// Single category id
$post->attachCategories(1);

// Multiple category IDs array
$post->attachCategories([1, 2, 5]);

// Multiple category IDs collection
$post->attachCategories(collect([1, 2, 5]));

// Single category model instance
$categoryInstance = app('rinvex.categories.category')->first();
$post->attachCategories($categoryInstance);

// Single category slug
$post->attachCategories('test-category');

// Multiple category slugs array
$post->attachCategories(['first-category', 'second-category']);

// Multiple category slugs collection
$post->attachCategories(collect(['first-category', 'second-category']));

// Multiple category model instances
$categoryInstances = app('rinvex.categories.category')->whereIn('id', [1, 2, 5])->get();
$post->attachCategories($categoryInstances);

注意

  • attachCategories() 方法将给定的分类附加到模型,而不会影响当前附加的分类。而 syncCategories() 方法可以移除不在给定项目中的任何记录,此方法接受一个可选的布尔参数,该参数将分离标志设置为 truefalse
  • 要移除模型分类,您可以使用 detachCategories() 方法,该方法与 attachCategories() 方法具有完全相同的签名,并且具有附加功能,可以通过传递 null 或空值来移除所有当前附加的分类: $post->detachCategories();

如您所预期的那样,您还可以检查是否已附加分类

// Single category id
$post->hasAnyCategories(1);

// Multiple category IDs array
$post->hasAnyCategories([1, 2, 5]);

// Multiple category IDs collection
$post->hasAnyCategories(collect([1, 2, 5]));

// Single category model instance
$categoryInstance = app('rinvex.categories.category')->first();
$post->hasAnyCategories($categoryInstance);

// Single category slug
$post->hasAnyCategories('test-category');

// Multiple category slugs array
$post->hasAnyCategories(['first-category', 'second-category']);

// Multiple category slugs collection
$post->hasAnyCategories(collect(['first-category', 'second-category']));

// Multiple category model instances
$categoryInstances = app('rinvex.categories.category')->whereIn('id', [1, 2, 5])->get();
$post->hasAnyCategories($categoryInstances);

注意

  • hasAnyCategories() 方法检查模型是否已附加了任何给定的分类。它返回布尔值 truefalse
  • 类似地,hasAllCategories() 方法与 hasAnyCategories() 方法具有完全相同的签名,但它执行不同的操作,并执行严格的比较以检查是否已附加了所有给定的分类。

高级用法

生成分类缩略名

Rinvex Categories 自动生成缩略名,并在未提供时自动检测并插入默认翻译。但您仍然可以通过常规 Eloquent create 方法显式传递它,如下所示

app('rinvex.categories.category')->create(['name' => ['en' => 'My New Category'], 'slug' => 'custom-category-slug']);

注意:请参阅 Sluggable 包以获取更多详细信息。

智能参数检测

瑞文克斯分类 方法能够智能地处理列表中的分类,几乎可以处理所有类型的输入,如上例所示。它会检查输入类型并相应地处理。

检索所有与分类关联的模型

你可能需要获取特定分类的所有模型,你可以轻松地按照以下步骤进行

$category = app('rinvex.categories.category')->find(1);
$category->entries(\App\Models\Post::class)->get();

查询范围

是的,瑞文克斯分类 为您提供了一些方便的查询范围,使用示例

// Single category id
$post->withAnyCategories(1)->get();

// Multiple category IDs array
$post->withAnyCategories([1, 2, 5])->get();

// Multiple category IDs collection
$post->withAnyCategories(collect([1, 2, 5]))->get();

// Single category model instance
$categoryInstance = app('rinvex.categories.category')->first();
$post->withAnyCategories($categoryInstance)->get();

// Single category slug
$post->withAnyCategories('test-category')->get();

// Multiple category slugs array
$post->withAnyCategories(['first-category', 'second-category'])->get();

// Multiple category slugs collection
$post->withAnyCategories(collect(['first-category', 'second-category']))->get();

// Multiple category model instances
$categoryInstances = app('rinvex.categories.category')->whereIn('id', [1, 2, 5])->get();
$post->withAnyCategories($categoryInstances)->get();

注意

  • withAnyCategories() 范围可以找到具有给定任何关联分类的文章。它通常返回查询构建器,因此您可以将其链接或调用 get() 方法,例如,以执行并获取结果。
  • 类似地,还有一些其他范围,如 withAllCategories(),可以找到具有给定所有关联分类的文章,withoutCategories() 可以找到没有给定任何关联分类的文章,最后是 withoutAnyCategories(),它找到没有任何关联分类的文章。所有范围都是平等创建的,具有相同的签名,并返回查询构建器。

分类翻译

按照以下方式轻松管理分类翻译

$category = app('rinvex.categories.category')->find(1);

// Update title translations
$category->setTranslation('name', 'en', 'New English Category Title')->save();

// Alternatively you can use default eloquent update
$category->update([
    'name' => [
        'en' => 'New Category',
        'ar' => 'تصنيف جديد',
    ],
]);

// Get single category translation
$category->getTranslation('name', 'en');

// Get all category translations
$category->getTranslations('name');

// Get category title in default locale
$category->name;

注意:有关详细信息,请查看 可翻译 包。

管理您的节点/嵌套集

插入分类

移动和插入分类包括多个数据库查询,因此当分类保存时,将自动开始 事务。如果您正在处理多个模型,则可以安全地使用全局事务。

另一个重要注意事项是,结构操作被延迟,直到您在模型上调用 save(一些方法隐式调用 save 并返回操作的真值结果)。

如果模型成功保存,并不意味着分类已移动。如果您的应用程序依赖于分类是否实际改变了位置,请使用 hasMoved 方法

if ($category->save()) {
    $moved = $category->hasMoved();
}

创建分类

当您简单地创建一个分类时,它将被附加到树的末尾

app('rinvex.categories.category')->create($attributes); // Saved as root

$category = app('rinvex.categories.category')->fill($attributes);
$category->save(); // Saved as root

在这种情况下,该分类被认为是 ,这意味着它没有父级。

从现有分类创建根

分类将被附加到树的末尾

// #1 Implicit save
$category->saveAsRoot();

// #2 Explicit save
$category->makeRoot()->save();

附加和前置到指定的父级

如果您想将分类作为其他分类的子分类,您可以将其设置为最后一个或第一个子分类。假设 $parent 是某个现有分类,以下是一些附加分类的方法

// #1 Using deferred insert
$category->appendToNode($parent)->save();

// #2 Using parent category
$parent->appendNode($category);

// #3 Using parent's children relationship
$parent->children()->create($attributes);

// #5 Using category's parent relationship
$category->parent()->associate($parent)->save();

// #6 Using the parent attribute
$category->parent_id = $parent->getKey();
$category->save();

// #7 Using static method
app('rinvex.categories.category')->create($attributes, $parent);

只有几种方法可以前置

// #1 Using deferred insert
$category->prependToNode($parent)->save();

// #2 Using parent category
$parent->prependNode($category);

在指定分类之前或之后插入

您可以将 $category 设置为与 $neighbor 分类相邻。假设 $neighbor 是某个现有分类,而目标分类可以是新的。如果目标分类存在,它将被移动到新位置,并且如果需要,父级将更改。

# Explicit save
$category->afterNode($neighbor)->save();
$category->beforeNode($neighbor)->save();

# Implicit save
$category->insertAfterNode($neighbor);
$category->insertBeforeNode($neighbor);

从数组构建树

当在分类上使用静态方法 create 时,它会检查属性是否包含 children 键。如果包含,它将递归地创建更多分类,如下所示

$category = app('rinvex.categories.category')->create([
    'name' => [
        'en' => 'New Category Title',
    ],

    'children' => [
        [
            'name' => 'Bar',

            'children' => [
                [ 'name' => 'Baz' ],
            ],
        ],
    ],
]);

$category->children 现在包含已创建的子分类列表。

从数组重新构建树

您可以轻松地重新构建树。这对于大量更改树的结构非常有用。给定 $data 作为分类数组,您可以按照以下方式构建树

$data = [
    [ 'id' => 1, 'name' => 'foo', 'children' => [ ... ] ],
    [ 'name' => 'bar' ],
];

app('rinvex.categories.category')->rebuildTree($data, $delete);

为标题为 foo 的类别指定了 ID,这意味着现有类别将被填充并保存。如果类别不存在,将抛出 ModelNotFoundException。此外,此类别还指定了 children,它也是一个类别数组;它们将以相同的方式处理并保存为 foo 类别的子类别。

类别 bar 没有指定主键,因此它将被视为新类别,并被创建。

$delete 表示是否删除在 $data 中不存在但已存在的类别。默认情况下,类别不会被删除。

检索分类

在某些情况下,我们将使用一个 $id 变量,它是目标类别的 ID。

祖先

祖先构成类别的父级链。这对于显示当前类别的面包屑很有用。

// #1 Using accessor
$result = $category->getAncestors();

// #2 Using a query
$result = $category->ancestors()->get();

// #3 Getting ancestors by primary key
$result = app('rinvex.categories.category')->ancestorsOf($id);

后代

后代是指子树中的所有类别,即类别的子类别、子类别的子类别等。

// #1 Using relationship
$result = $category->descendants;

// #2 Using a query
$result = $category->descendants()->get();

// #3 Getting descendants by primary key
$result = app('rinvex.categories.category')->descendantsOf($id);

// #3 Get descendants and the category by id
$result = app('rinvex.categories.category')->descendantsAndSelf($id);

后代可以被预加载

$categories = app('rinvex.categories.category')->with('descendants')->whereIn('id', $idList)->get();

兄弟

兄弟是具有相同父级的类别。

$result = $category->getSiblings();

$result = $category->siblings()->get();

仅获取下一个兄弟

// Get a sibling that is immediately after the category
$result = $category->getNextSibling();

// Get all siblings that are after the category
$result = $category->getNextSiblings();

// Get all siblings using a query
$result = $category->nextSiblings()->get();

获取上一个兄弟

// Get a sibling that is immediately before the category
$result = $category->getPrevSibling();

// Get all siblings that are before the category
$result = $category->getPrevSiblings();

// Get all siblings using a query
$result = $category->prevSiblings()->get();

从其他表中获取相关模型

想象一下,每个类别 has many 产品。即 HasMany 关系已建立。您如何获取 $category 和其所有后代的全部产品?很简单!

// Get ids of descendants
$categories = $category->descendants()->pluck('id');

// Include the id of category itself
$categories[] = $category->getKey();

// Get products
$goods = Product::whereIn('category_id', $categories)->get();

现在想象一下,每个类别 has many 帖子。即这次建立 morphToMany 关系。您如何获取 $category 和其所有后代的全部帖子?这可能吗?当然可以!

// Get ids of descendants
$categories = $category->descendants()->pluck('id');

// Include the id of category itself
$categories[] = $category->getKey();

// Get posts
$posts = \App\Models\Post::withCategories($categories)->get();

包括分类深度

如果您需要知道类别处于哪个级别

$result = app('rinvex.categories.category')->withDepth()->find($id);

$depth = $result->depth;

根类别将在级别 0。根类别的子类别将具有级别 1,等等。要获取指定级别的类别,您可以在查询构建器上应用 having 约束

$result = app('rinvex.categories.category')->withDepth()->having('depth', '=', 1)->get();

默认排序

每个类别都有其唯一的 _lft 值,该值决定了其在树中的位置。如果您想按此值对类别进行排序,您可以在查询构建器上使用 defaultOrder 方法

// All categories will now be ordered by lft value
$result = app('rinvex.categories.category')->defaultOrder()->get();

您可以按相反的顺序获取类别

$result = app('rinvex.categories.category')->reversed()->get();
移动分类

为了在父类别内部上移或下移类别以影响默认顺序

$bool = $category->down();
$bool = $category->up();

// Shift category by 3 siblings
$bool = $category->down(3);

操作的结果是布尔值,表示类别是否已更改其位置。

约束

可以应用于查询构建器的各种约束

  • whereIsRoot() 获取仅根类别;
  • whereIsAfter($id) 获取每个在指定 ID 类别之后(不仅是兄弟)的类别;
  • whereIsBefore($id) 获取在指定 ID 类别之前的每个类别。

后代约束

$result = app('rinvex.categories.category')->whereDescendantOf($category)->get();
$result = app('rinvex.categories.category')->whereNotDescendantOf($category)->get();
$result = app('rinvex.categories.category')->orWhereDescendantOf($category)->get();
$result = app('rinvex.categories.category')->orWhereNotDescendantOf($category)->get();

// Include target category into result set
$result = app('rinvex.categories.category')->whereDescendantOrSelf($category)->get();

祖先约束

$result = app('rinvex.categories.category')->whereAncestorOf($category)->get();

$category 可以是模型的 primary key 或模型实例。

构建树

在获取一组类别后,您可以将其转换为树。例如

$tree = app('rinvex.categories.category')->get()->toTree();

这将填充集合中每个类别的 parentchildren 关系,您可以使用递归算法渲染树

$categories = app('rinvex.categories.category')->get()->toTree();

$traverse = function ($categories, $prefix = '-') use (&$traverse) {
    foreach ($categories as $category) {
        echo PHP_EOL.$prefix.' '.$category->name;

        $traverse($category->children, $prefix.'-');
    }
};

$traverse($categories);

这将输出以下内容

- Root
-- Child 1
--- Sub child 1
-- Child 2
- Another root
构建平面树

此外,您还可以构建一个平面树:一个类别列表,其中子类别直接跟在父类别后面。这在您获取具有自定义排序(例如按字母顺序)的类别且不想使用递归遍历类别时很有用。

$categories = app('rinvex.categories.category')->get()->toFlatTree();
获取子树

有时您不需要加载整个树,只需要加载特定类别的子树

$root = app('rinvex.categories.category')->find($rootId);
$tree = $root->descendants->toTree($root);

现在 $tree 包含 $root 类别的子类别。

如果您不需要 $root 类别本身,可以这样做

$tree = app('rinvex.categories.category')->descendantsOf($rootId)->toTree($rootId);

删除分类

要删除类别

$category->delete();

重要!类别拥有的任何后代也将被 删除

重要!必须将类别作为模型进行删除,不要尝试使用如下查询删除它们

app('rinvex.categories.category')->where('id', '=', $id)->delete();

这将破坏树!

辅助方法

// Check if category is a descendant of other category
$bool = $category->isDescendantOf($parent);

// Check whether the category is a root:
$bool = $category->isRoot();

// Other checks
$category->isChildOf($other);
$category->isAncestorOf($other);
$category->isSiblingOf($other);

检查一致性

您可以检查树是否已损坏(即存在某些结构错误)

// Check if tree is broken
$bool = app('rinvex.categories.category')->isBroken();

// Get tree error statistics
$data = app('rinvex.categories.category')->countErrors();

树错误统计将返回一个包含以下键的数组

  • oddness -- 具有错误 lftrgt 值的类别数量
  • duplicates -- 具有相同 lftrgt 值的类别数量
  • wrong_parent -- 具有无效的 parent_id 值(不对应 lftrgt 值)的分类数量
  • missing_parent -- 指向不存在分类的 parent_id 的分类数量

修复树

分类树现在可以修复(如果已损坏)。使用 parent_id 列的继承信息,为每个分类设置适当的所有 _lft_rgt 值。

app('rinvex.categories.category')->fixTree();

注意:有关详细信息,请参阅 Nested Sets 包。

变更日志

请参阅 变更日志,了解项目的完整历史。

支持

以下支持渠道随时为您服务

贡献 & 协议

感谢您考虑为该项目做出贡献!贡献指南可在 CONTRIBUTING.md 中找到。

欢迎提交错误报告、功能请求和拉取请求。

安全漏洞

我们希望确保此包对每个人都安全。如果您在此包中发现安全漏洞,我们感谢您以 负责任的方式 向我们披露。

公开披露漏洞可能会使整个社区面临风险。如果您发现安全担忧,请通过电子邮件发送至 help@rinvex.com。我们将与您合作,确保我们理解问题的范围,并完全解决您的担忧。我们将视为 help@rinvex.com 的最高优先级,并尽快解决任何问题。

安全漏洞得到纠正后,将尽快部署安全热修复版本。

关于 Rinvex

Rinvex 是一家软件解决方案初创公司,自 2016 年 6 月在埃及亚历山大成立以来,专门为中小企业提供集成企业解决方案。我们相信,我们的动力——价值、范围和影响——是我们与众不同的地方,通过软件的力量释放我们哲学的无限可能性。我们喜欢称之为“生活的速度创新”。这就是我们如何为推动人类进步做出我们的一份贡献。

许可

此软件根据 MIT 许可证 (MIT) 发布。

(c) 2016-2020 Rinvex LLC,部分权利保留。