laravel中的动态类别级别 [英] dynamic category level in laravel
问题描述
我的laravel应用程序中有3个深层次的类别,例如
Parent
- Child One
-- Child Two
当我在菜单,帖子详细信息等不同部分中使用此嵌套类别时,一切都很好,但最近我遇到了一个问题,需要指导来解决.
问题
如果我有任何帖子包含child one
或child 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
在Blade中创建太多if语句来确保我们为正确的类别找到正确的路线似乎不正确.我在考虑返回最终路线的模型函数取决于数据库中类别育儿级别,但是我不确定是否可行?如果是的话,怎么办?
问题
- 是否可以将类别路由传递给模型中的类别?
- 该怎么做?
代码
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->route}}
用作我的href 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 = [])
{
想法?
可以,但是您必须注意脚本的性能,因为这样做最终可能需要完成许多数据库查询才能确定每个类别路由.>
我的建议是创建一个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->route
,就可以在刀片视图中使用它.
@foreach($post->categories as $category)
<a class="post-cat" href="{{$category->route}}">{{$category->title}}</a>
@endforeach
请注意,可能还有其他解决方案比这更好.我只是在没有过多思考的情况下碰到了这一点.另外,上面的代码尚未经过测试,因此请注意,可能需要进行一些较小的更改才能使其正常工作.
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?
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.
这篇关于laravel中的动态类别级别的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!