dqan70724 2018-11-01 09:05
浏览 64

laravel中的动态类别级别

I have 3 level deep categories in my laravel application like

Parent
 - Child One
  -- Child Two

When I use this nested categories in different parts such as menu, posts details etc. everything is just fine but recently I came cross an issue and I need guide to solve it.

The issue

If any of my posts includes child one or child two level category it's a bit hard to provide correct route path for it, EXAMPLE:

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

creating this much if statement in blade to make sure we get the right route for the right categories to me doesn't seem right. I was thinking about model function which returns final route depend on category parenting level in database but I wasn't sure if it's possible or not? and if it is, how?

Question

  1. Is it possible I pass category routes to the categories from model?
  2. How to do that?

Code

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');
    }

this is how currently i'm getting my posts categories:

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

Any idea?

UPDATE

Well I solved it :-D here is the code I've made

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

This does exactly what I needed it return slugs by orders like parent/child1/child2

Issue

the issue here is now routing this dynamic path as the result of this function I can now have any deep level of path and this needs to be dynamic in routes as well.

Route

my current route is like:

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

which returns this path:

site.com/category/parent

but it doesn't return:

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'));
}

any idea?

Update 2

based on Matei Mihai answer I've made custom classes in App/Helpers folder with details below:

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

and 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();
        });
    }
}

then I registered my provider to composer.json file like:

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

I also dumped autoloads and added this to my Category model

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

then I used {{$category->route}} as my categries href attribute and I got this:

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

which is refers to:

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

ideas?

  • 写回答

1条回答 默认 最新

  • duanfangbunao36970 2018-11-01 10:12
    关注

    It is possible, but you must take care of the script performance because it could end up having a lot of DB queries done to determine each category route.

    My recommendation would be to create a CategoryRouteService class which will be registered as singleton to keep the database queries as low as possible. It could be something like:

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

    Now as I said before, you need a service provider to register this service. It should look like this:

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

    This service provider must be added in the app configuration file.

    The Category model could have a method which defines the route attribute:

    class Category extends Model
    {
        public function getRouteAttribute()
        {
            $categoryRouteService = app(CategoryRouteService::class);
    
            return $categoryRouteService->getRoute($this);
        }
    
        /** Your relationships methods **/
    }
    

    Finally, you can use it in your blade views simply by using $category->route.

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

    Please note that there could be other solutions better than this. I just came across this one without thinking too much. Also, the code above was not tested so please be aware that it might need some minor changes to make it work properly.

    评论

报告相同问题?

悬赏问题

  • ¥20 完全没有学习过GAN,看了CSDN的一篇文章,里面有代码但是完全不知道如何操作
  • ¥15 使用ue5插件narrative时如何切换关卡也保存叙事任务记录
  • ¥20 软件测试决策法疑问求解答
  • ¥15 win11 23H2删除推荐的项目,支持注册表等
  • ¥15 matlab 用yalmip搭建模型,cplex求解,线性化处理的方法
  • ¥15 qt6.6.3 基于百度云的语音识别 不会改
  • ¥15 关于#目标检测#的问题:大概就是类似后台自动检测某下架商品的库存,在他监测到该商品上架并且可以购买的瞬间点击立即购买下单
  • ¥15 神经网络怎么把隐含层变量融合到损失函数中?
  • ¥15 lingo18勾选global solver求解使用的算法
  • ¥15 全部备份安卓app数据包括密码,可以复制到另一手机上运行