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
- Is it possible I pass category routes to the categories from model?
- 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?