rinvex/categories

此包已废弃,不再维护。作者建议使用 rinvex/laravel-categories 包代替。

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

v0.0.6 2018-09-29 02:21 UTC

This package is auto-updated.

Last update: 2022-02-01 13:09:41 UTC


README

Rinvex Categories 是一个多态 Laravel 包,用于分类管理。您可以轻松地将任何 Eloquent 模型分类,并利用 Nested SetsSluggableTranslatable 模型的力量。

⚠️ 此包已重命名,现在在 rinvex/laravel-categories 维护,作者建议使用新包。旧包支持 Laravel v5.6,而新包支持 Laravel v5.7+。

Packagist Scrutinizer Code Quality Code Climate Travis StyleCI License

安装

  1. 使用 composer 安装包

    composer require rinvex/categories
  2. 执行以下命令执行迁移

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

使用方法

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

管理您的分类

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

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

管理您的可分类模型

API 直观且非常简单,让我们快速了解一下

// 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 包。

智能参数检测

Rinvex Categories 方法可以智能地处理几乎所有的输入,如上述示例所示。它将检查输入类型并相应地执行。

检索附加到类别的所有模型

您可能遇到需要获取附加到特定类别中的所有模型的情况,您可以轻松地如下操作

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

查询作用域

是的,Rinvex Categories 随带一些很酷的查询作用域,方便您使用,例如

// 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;

注意:有关详细信息,请查看 Translatable 包。

管理您的节点/嵌套集

插入类别

移动和插入类别涉及多个数据库查询,因此当类别保存时,自动开始事务。如果您处理多个模型,则可以使用全局事务。

另一个重要说明是,结构操作将延迟到你在模型上点击 保存 时执行(某些方法会隐式调用 保存 并返回操作的结果布尔值)。

如果模型成功保存,并不意味着类别已被移动。如果您的应用程序依赖于类别是否实际改变了位置,请使用 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();

从其他表获取相关模型

想象一下,每个分类 有多个 产品。也就是说,建立了 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();

现在想象一下,每个分类 有多个 帖子。即这次建立了 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 可以是模型的键或模型实例。

构建树

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

$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();

这将破坏树!

SoftDeletes 特性在模型级别也得到了支持。

辅助方法

// 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 -- 具有不对应于lftrgt值的无效parent_id值的类别数量
  • 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-2018 Rinvex LLC,部分权利保留。