【问题标题】:dynamic category level in laravellaravel 中的动态类别级别
【发布时间】:2019-04-05 11:41:54
【问题描述】:

我的 laravel 应用程序中有 3 个级别的深度类别,例如

Parent
 - Child One
  -- Child Two

当我在菜单、帖子详细信息等不同部分使用此嵌套类别时,一切都很好,但最近我遇到了一个问题,我需要指导来解决它。

问题

如果我的任何帖子包含child onechild two 级别类别,则很难为其提供正确的路线路径,例如:

Parent route :  site.com/p/slug
Child one route: site.com/parent_slug/childOne_slug
Child two route: site.com/parent_slug/childOne_slug/childTwo_slug

在刀片中创建这么多 if 语句以确保我们为正确的类别获得正确的路线对我来说似乎不正确。我正在考虑返回最终路线的模型函数取决于数据库中的类别育儿级别,但我不确定它是否可能?如果是,如何?

问题

  1. 是否可以将类别路由传递给模型中的类别?
  2. 怎么做?

代码

Category model

protected $fillable = [
        'title', 'slug', 'thumbnail', 'publish','mainpage', 'parent_id', 'color', 'template'
    ];

    public function posts(){
        return $this->belongsToMany(Post::class, 'post_categories');
    }

    public function parent() {
        return $this->belongsTo(Category::class, 'parent_id');
    }

    public function children() {
        return $this->hasMany(Category::class, 'parent_id');
    }

这是我目前获取帖子类别的方式:

@foreach($post->categories as $category)
  <a class="post-cat" href="#">{{$category->title}}</a>
@endforeach

有什么想法吗?

更新

好吧,我解决了:-D 这是我制作的代码

public function getCatSlug(){

        if($this->parent_id != ''){ //child one

            if($this->parent->parent_id != ''){ //child two

                if($this->parent->parent->parent_id != ''){ //child three
                    return $this->parent->parent->parent->slug. '/'. $this->parent->parent->slug. '/'. $this->parent->slug. '/'. $this->slug;
                }

                return $this->parent->parent->slug. '/'. $this->parent->slug. '/'. $this->slug;
            }

            return $this->parent->slug. '/'. $this->slug;
        }

        return $this->slug;
    }

这正是我所需要的,它通过像parent/child1/child2 这样的命令返回蛞蝓

问题

这里的问题现在是路由这个动态路径,因为这个函数的结果我现在可以有任何深层路径,这也需要在路由中是动态的。

路线

我现在的路线是这样的:

Route::get('/category/{slug}', 'Front\CategoryController@parent')->name('categoryparent');

返回此路径:

site.com/category/parent

但它没有返回:

site.com/category/parent/child_one
site.com/category/parent/child_one/child_two

Controller

public function parent($slug){
  $category = Category::where('slug', $slug)->with('children')->first();
  $category->addView();
  $posts = $category->posts()->paginate(8);
  return view('front.categories.single', compact('category','posts'));
}

有什么想法吗?

更新 2

基于Matei Mihai 的答案,我在App/Helpers 文件夹中创建了自定义类,详细信息如下:

CategoryRouteService.php

<?php

namespace App\Helpers;

use App\Category;

class CategoryRouteService
{
    private $routes = [];

    public function __construct()
    {
        $this->determineCategoriesRoutes();
    }

    public function getRoute(Category $category)
    {
        return $this->routes[$category->id];
    }

    private function determineCategoriesRoutes()
    {
        $categories = Category::all()->keyBy('id');

        foreach ($categories as $id => $category) {
            $slugs = $this->determineCategorySlugs($category, $categories);

            if (count($slugs) === 1) {
                $this->routes[$id] = url('p/' . $slugs[0]);
            }
            else {
                $this->routes[$id] = url('/' . implode('/', $slugs));
            }
        }
    }

    private function determineCategorySlugs(Category $category, Collection $categories, array $slugs = [])
    {
        array_unshift($slugs, $category->slug);

        if (!is_null($category->parent_id)) {
            $slugs = $this->determineCategorySlugs($categories[$category->parent_id], $categories, $slugs);
        }

        return $slugs;
    }
}

CategoryServiceProvider.php

<?php

namespace App\Helpers;

use App\Helpers\CategoryRouteService;

class CategoryServiceProvider
{
    public function register()
    {
        $this->app->singleton(CategoryRouteService::class, function ($app) {
            // At this point the categories routes will be determined.
            // It happens only one time even if you call the service multiple times through the container.
            return new CategoryRouteService();
        });
    }
}

然后我将我的提供程序注册到composer.json 文件,例如:

"autoload": {
        "classmap": [
            "database/seeds",
            "database/factories"
        ],
        "psr-4": {
            "App\\": "app/"
        },
        "files": [
            "app/helpers/CategoryServiceProvider.php" //added here
        ]
    },

我还转储了自动加载并将其添加到我的 Category 模型中

use App\Helpers\CategoryRouteService;
public function getRouteAttribute()
{
  $categoryRouteService = app(CategoryRouteService::class);
  return $categoryRouteService->getRoute($this);
}

然后我使用 {{$category-&gt;route}} 作为我的类别 href 属性,我得到了这个:

Argument 2 passed to App\Helpers\CategoryRouteService::determineCategorySlugs() must be an instance of App\Helpers\Collection, instance of Illuminate\Database\Eloquent\Collection given

指的是:

private function determineCategorySlugs(Category $category, Collection $categories, array $slugs = [])
    {

想法?

【问题讨论】:

  • 模型方法将是解决此问题的好方法。诀窍是递归查找父类别(及其父类别等),直到您发现您已经找到顶级类别。然后,您可以将找到的每个类别的 slug 传递到您的路线中。如果您需要进一步的帮助,请试一试并发布您的代码。在这种情况下查看您的路线定义也会很有帮助,如果您可以发布它 - 这将使我们能够进一步帮助您。
  • @Jonathon 已更新。
  • @mafortis,我使用你的代码。但我的$category-&gt;route 有错误。 Undefined offset: 0 。可以帮我吗?

标签: php laravel


【解决方案1】:

这是可能的,但您必须注意脚本性能,因为它最终可能会执行大量数据库查询来确定每个类别路径。

我的建议是创建一个CategoryRouteService 类,该类将注册为单例,以使数据库查询尽可能低。可能是这样的:

class CategoryRouteService
{
    private $routes = [];

    public function __construct()
    {
        $this->determineCategoriesRoutes();
    }

    public function getRoute(Category $category)
    {
        return $this->routes[$category->id];
    }

    private function determineCategoriesRoutes()
    {
        $categories = Category::all()->keyBy('id');

        foreach ($categories as $id => $category) {
            $slugs = $this->determineCategorySlugs($category, $categories);

            if (count($slugs) === 1) {
                $this->routes[$id] = url('/p/' . $slugs[0]);
            }
            else {
                $this->routes[$id] = url('/' . implode('/', $slugs));
            }
        }
    }

    private function determineCategorySlugs(Category $category, Collection $categories, array $slugs = [])
    {
        array_unshift($slugs, $category->slug);

        if (!is_null($category->parent_id)) {
            $slugs = $this->determineCategorySlugs($categories[$category->parent_id], $categories, $slugs);
        }

        return $slugs;
    }
}

现在正如我之前所说,您需要一个服务提供商来注册此服务。它应该是这样的:

class CategoryServiceProvider
{
    public function register()
    {
        $this->app->singleton(CategoryRouteService::class, function ($app) {
            // At this point the categories routes will be determined.
            // It happens only one time even if you call the service multiple times through the container.
            return new CategoryRouteService();
        });
    }
}

此服务提供者必须添加到应用配置文件中。

Category 模型可以有一个定义route 属性的方法:

class Category extends Model
{
    public function getRouteAttribute()
    {
        $categoryRouteService = app(CategoryRouteService::class);

        return $categoryRouteService->getRoute($this);
    }

    /** Your relationships methods **/
}

最后,您只需使用$category-&gt;route,即可在刀片视图中使用它。

@foreach($post->categories as $category)
  <a class="post-cat" href="{{$category->route}}">{{$category->title}}</a>
@endforeach

请注意,可能还有比这更好的解决方案。我只是偶然发现了这个,没有想太多。另外,上面的代码没有经过测试,所以请注意它可能需要一些小的改动才能使其正常工作。

【讨论】:

  • 嗨,谢谢您的回答,当您发布此答案时,我正在更新我的问题。您介意检查我的更新,看看您的答案是否有效或需要在我测试之前进行一些更改吗?谢谢
  • 我看到了你更新的问题。那里的问题正是我在答案中写的第一件事。如果您有 100 个类别,您最终将至少有 3-400 个数据库查询来确定类别路由。使用我的解决方案将只有 1 个查询
  • 听起来很完美,但我没有制作服务的经验。是否有任何命令或如何?我应该在哪里做?
  • 您可以在应用程序的任何位置创建一个新目录并在其中创建这些类。此外,您必须为它们添加正确的命名空间。喜欢App\MyCategoriesDirectory
  • 我在App 文件夹中创建了名为Helpers 的文件夹,这是内容,您能确认我做得好不好? collabedit.com/canrk
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-02
  • 2020-12-08
  • 1970-01-01
  • 2017-09-05
相关资源
最近更新 更多