I have complex query and relation which I'm not fully understand. I'm kind of new in Laravel. Anyway, I'm looking for a way to load this with slugs instead of ID's.
This is the function in the controller
public function index( $category_id)
{
$Category = new Category;
$allCategories = $Category->getCategories();
$category = Category::find($category_id);
if($category->parent_id == 0) {
$ids = Category::select('id')->where('parent_id', $category_id)->where('parent_id','!=',0)->get();
$array = array();
foreach ($ids as $id) {
$array[] = (int) $id->id;
}
$items = Item::whereIn('category_id',$array)->where('published', 1)->paginate(5);
} else {
$items = Item::where('category_id' ,$category_id)->where('published', 1)->paginate(5);
}
return view('list', compact('allCategories','items'));
}
Those are relations in the Model
public function item()
{
return $this->hasMany('App\Item','category_id');
}
public function children()
{
return $this->hasMany('App\Category', 'parent_id');
}
public function getCategories()
{
$categoires = Category::where('parent_id',0)->get();
$categoires = $this->addRelation($categoires);
return $categoires;
}
public function selectChild( $id )
{
$categoires = Category::where('parent_id',$id)->where('published', 1)->paginate(40);
$categoires = $this->addRelation($categoires);
return $categoires;
}
public function addRelation( $categoires )
{
$categoires->map(function( $item, $key)
{
$sub = $this->selectChild($item->id);
$item->itemCount = $this->getItemCount($item->id , $item->parent_id );
return $item = array_add($item, 'subCategory', $sub);
});
return $categoires;
}
public function getItemCount( $category_id )
{
return Item::where('category_id', $category_id)->count();
}
This is what I have in my routes
Route::get('list/{category}', 'ListController#index')->name('list');
currently is loading urls like http://example.com/list/1 where 1 is the ID. I'm wonder if with current setup is possible to make it like, http://example.com/slug
I'm aware how slugs are working. I just can't understand how to use them in queries instead of ID's
You can use explicit Route Model Binding to grab your Category by slug before processing it.
In your RouteServiceProvider you need to bind the model:
Route::bind('category', function ($value) {
//Change slug to your column name
return App\Category::where('slug', $value)->firstOrFail();
});
Then, you can typehint the categories.
For example in your index method:
public function index(Category $category)
{
$Category = new Category;
$allCategories = $Category->getCategories();
//This line is obsolete now:
//$category = Category::find($category_id);
//...
}
Try to change your index function parameter from $category_id to $category_slug
Remove this line $Category = new Category;
And change this $category = Category::find($category_id);
To this: $category = Category::where('slug', $category_slug)->first();
*Assuming that you have a unique slug in category table
Related
I have made dynamic category routes by adding custom class to my app (how i did it is here) now i need to make my blade works with this dynamic path.
Logic
based on categories deeps my url will create such as:
site.com/category/parent
site.com/category/parent/child
site.com/category/parent/child/child
etc.
so far my view is just loading for site.com/category/parent for other urls it return 404 error.
code
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('category/' . $slugs[0]);
}
else {
$this->routes[$id] = url('category/' . 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
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();
});
}
}
model
//get dynamic slug routes
public function getRouteAttribute()
{
$categoryRouteService = app(CategoryRouteService::class);
return $categoryRouteService->getRoute($this);
}
blade
//{{$categoryt->route}} returning routes
<a class="post-cat" href="{{$category->route}}">{{$category->title}}</a>
route
//show parent categories with posts
Route::get('/category/{slug}', 'Front\CategoryController#parent')->name('categoryparent');
controller
public function parent($slug){
$category = Category::where('slug', $slug)->with('children')->first();
$category->addView();
$posts = $category->posts()->where('publish', '=', 1)->paginate(8);
return view('front.categories.single', compact('category','posts'));
}
Note: I'm not sure about this but i think i my route is kinda static! I mean it just getting 1 slug with it while my category can goes 2, 3 or 4 slug deep and it doesn't make sense to me to make several route and keep repeating Route::get('/category/{slug}/{slug}/{slug} like that.
As I said I'm not sure about this, please share your idea and solutions if you may.
UPDATE
based on Leena Patel answer I changed my route but when I get more than 1 slug in my url it returns error:
Example
route: site.com/category/resources (works)
route: site.com/category/resources/books/ (ERROR)
route: site.com/category/resources/books/mahayana/sutra (ERROR)
error
Call to a member function addView() on null
on
$category->addView();
when I comment that it returns error for $posts part. then error for my blade where i returned category title {{$category->title}}
So basically it seem doesn't recognize this function for returning view of category routes.
here is my function
public function parent($slug){
$category = Category::where('slug', $slug)->with('children')->first();
$category->addView();
$posts = $category->posts()->where('publish', '=', 1)->paginate(8);
return view('front.categories.single', compact('category','posts'));
}
any idea?
You can try using Route Pattern like below
Route::get('/category/{slug}', 'Front\CategoryController#parent')->where('slug','.+')->name('categoryparent')
So if you have more than one slugs in your url like /category/slug1/slug2
Your addView() method will work for one record and not for Collection So add foreach loop to achieve this.
public function parent($slug){
// $slug will be `slug1/slug2`
$searchString = '/';
$posts = array();
if( strpos($slug, $searchString) !== false ) {
$slug_array = explode('/',$slug);
}
if(isset($slug_array))
{
foreach($slug_array as $slug)
{
$category = Category::where('slug', $slug)->with('children')->first();
$category->addView();
$posts_array = $category->posts()->where('publish', '=', 1)->paginate(8);
array_push($posts,$posts_array);
}
}
else
{
$category = Category::where('slug', $slug)->with('children')->first();
$category->addView();
$posts = $category->posts()->where('publish', '=', 1)->paginate(8);
}
return view('front.categories.single', compact('category','posts'));
}
Hope it helps!
Documentation : https://laravel.com/docs/4.2/routing#route-parameters
Create route
Route::get('category/{cat}', 'YourController#mymethod');
Add this to your Providers/RouteServiceProvider.php 's boot method
public function boot()
{
Route::pattern('cat', '.+'); //add this
parent::boot();
}
In your method:
public function mymethod($cat){
echo $cat; //access your route
}
You can use optional URL sections in the route and use conditionals in controllers. Try this:
In your route:
Route::get('/category/{parent?}/{child1?}/{child2?}', 'Front\CategoryController#parent')->name('categoryparent');
In your controller:
public function mymethod($category, $parent, $child1, $child2){
if(isset($child2)){
//use $category, $parent, $child1, $child2 and return view
} else if(isset($child1)){
//use $category, $parent, $child1 and return view
} else if(isset($parent)){
//use $category, $parent and return view
} else {
//return view for $category
}
}
I'm going to pass data in array
but here's the code
public function test($pid){
$row_for_menu = Menu::where('parent_id', '=', $pid)->get();
$menu = array();
foreach ($row_for_menu as $menu_one_by_one){
$menu[] = array('title' => $menu_one_by_one->name, 'nodes' => $this->test($menu_one_by_one->id));
}
dd($menu);
}
public function index(Request $request)
{
$this->test(1);
//$perPage = 25;
//$menu = Menu::paginate($perPage);
//return view('admin.menu.index', compact('menu'));
}
my foreach in first round worked well
but in the end responsed [ dd($menu) ] empty array and error 500 !
my english is not well sorry ;)
try this demo working fine
public function test($pid){
$row_for_menu = Menu::where('parent_id', '=', $pid)->get()->lists('id','name');
$menu = array();
//foreach ($row_for_menu as $menu_one_by_one){
//$menu[] = array('title' => $menu_one_by_one->name, 'nodes' => $this->test($menu_one_by_one->id));
//}
dd($menu);
}
$menu is empty ie. array() , because $row_for_menu is empty
public function test($pid){
$row_for_menu = Menu::where('parent_id', '=', $pid)->get();
$menu = array();
foreach ($row_for_menu as $menu_one_by_one){
$menu[] = array('title' => $menu_one_by_one->name, 'nodes' => $this->test($menu_one_by_one->id));
}
if(count($menu)){
dd($menu);
}
}
public function index(Request $request)
{
$this->test(1);
//$perPage = 25;
//$menu = Menu::paginate($perPage);
//return view('admin.menu.index', compact('menu'));
}
You should define a relation between your menu items on the model instead of recursively looping to build the menu.
// Menu model
public function parent ()
{
return $this->belongsTo(__CLASS__, 'parent_id');
}
public function children ()
{
return $this->hasMany(__CLASS__);
}
Then in your view:
#foreach ($menu->children as $child)
// $child->name
// $child->link
#endoreach
Your controller would simply be:
// assuming you pass a menu id for specific page or similar
public function index(Request $request) {
$menu = Menu::find($request->getAttribute('menu_id'))->toArray();
return view('admin.menu.index', compact('menu'));
}
the test function must return $menu after foreach like this
public function test($pid){
$menus = Menu::where('parent_id', '=', $pid)->get();
$menu = array();
foreach ($menus as $menu_one_by_one){
$menu[] = array('text' => $menu_one_by_one->name, 'nodes' => $this->test($menu_one_by_one->id));
}
return $menu;
}
public function index(Request $request)
{
$x = $this->test(1);
dd($menu)
How can I create a nested list of categories in Laravel?
I want to create something like this:
--- Php
------ Laravel
--------- Version
------------ V 5.7
--- Python
------ Django
--- Ruby
..........
The fields of my categories table are:
id | name | parent_id
If I have to add another column like depth or something, please tell me.
I am using this following code, but I think it is not the best solution. Besides, I can not pass this function to my view.
function rec($id)
{
$model = Category::find($id);
foreach ($model->children as $chield) rec($chield->id);
return $model->all();
}
function main () {
$models = Category::whereNull('parent_id')->get();
foreach ($models as $parent) return rec($parent->id);
}
You can make a self-referential model:
class Category extends Model {
public function parent()
{
return $this->belongsTo('Category', 'parent_id');
}
public function children()
{
return $this->hasMany('Category', 'parent_id');
}
}
and make a recursive relation:
// recursive, loads all descendants
public function childrenRecursive()
{
return $this->children()->with('childrenRecursive');
}
and to get parents with all their children:
$categories = Category::with('childrenRecursive')->whereNull('parent_id')->get();
Lastly you need to just iterate through the children until children is null. There can definitely be some performance issues with this if you are not careful. If this is a fairly small dataset that you plan to remain that way it shouldn't be an issue. If this is going to be an ever growing list it might make sense to have a root_parent_id or something to query off of and assemble the tree manually.
This function will return tree array:
function getCategoryTree($parent_id = 0, $spacing = '', $tree_array = array()) {
$categories = Category::select('id', 'name', 'parent_id')->where('parent_id' ,'=', $parent_id)->orderBy('parent_id')->get();
foreach ($categories as $item){
$tree_array[] = ['categoryId' => $item->id, 'categoryName' =>$spacing . $item->name] ;
$tree_array = $this->getCategoryTree($item->id, $spacing . '--', $tree_array);
}
return $tree_array;
}
If someone needs a better answer look up my answer, it helped me, when I had faced with such a problem.
class Category extends Model {
private $descendants = [];
public function subcategories()
{
return $this->hasMany(Category::class, 'parent_id');
}
public function children()
{
return $this->subcategories()->with('children');
}
public function hasChildren(){
if($this->children->count()){
return true;
}
return false;
}
public function findDescendants(Category $category){
$this->descendants[] = $category->id;
if($category->hasChildren()){
foreach($category->children as $child){
$this->findDescendants($child);
}
}
}
public function getDescendants(Category $category){
$this->findDescendants($category);
return $this->descendants;
}
}
And In your Controller just test this:
$category = Category::find(1);
$category_ids = $category->getDescendants($category);
It will result ids in array all descendants of your category where id=1.
then :
$products = Product::whereIn('category_id',$category_ids)->get();
You are welcome =)
Searching for something somehow in this area I wanted to share a functionality for getting the depth level of child:
function getDepth($category, $level = 0) {
if ($category->parent_id>0) {
if ($category->parent) {
$level++;
return $this->getDepth($category->parent, $level);
}
}
return $level;
}
Maybe it will help someone!
Cheers!
you can solve this problem like this :
class Category extends Model
{
public function categories()
{
return $this->hasMany(Category::class);
}
public function childrenCategories()
{
return $this->hasMany(Category::class)->with('categories');
}
}
and get category with children like this :
Category::whereNull('category_id')
->with('childrenCategories')
->get();
notice : just rename parent_id column to category_id
This also worked:
View:
$traverse = function ($categories) use (&$traverse) {
foreach ($categories as $category) $traverse($cat->Children);
};
$traverse(array ($category));
Model:
public function Children()
{
return $this->hasMany($this, 'parent');
}
public function Parent()
{
return $this->hasOne($this,'id','parent');
}
I am using laravel 5.4 and I want to filter search. its my front-end
my code looks like this
class DeviceFilterController extends Controller
{
public function filter(Feature $feature){
$marks = isset($_POST["mark"]) ? $_POST["mark"] : null;
$feature = $feature->newQuery();
if(isset($marks ))
{
foreach ($marks as $value)
{
$feature->where('device_mark', $value);
}
}
return $feature->get();
}
}
that result just one entry
You could take a different approach using whereIn(), assuming you mark input is an array of IDs like [1, 4, 8, 22].
public function filter(Request $request)
{
$features = Feature::when($request->has('mark'), function($query) use ($request) {
return $query->whereIn('device_mark', $request->mark); //Assuming you are passing an array of IDs
})->get();
return $features;
}
when() closure will only executes when you are sending 'mark' input. Doing it without when would look like this
public function filter(Request $request)
{
$features = [];
if ( $request->has('mark') && $request->mark != '' ) {
$features = Feature::whereIn('device_mark', $request->mark)->get();
} else {
$features = Feature::get();
}
return $features;
}
I have model for my post in my site.My post model has a column that stores visits.when user visit my post should increments by 1.
I exactly knows how to do this, but my problem is when I increment it by one in my model, it's increments by 2!!!!!
I wrote this code in my controller:
$new_post_inst = Post::where('id','=',$id)->first();
$new_post_inst->increment('visit');
This is my controller:
public function get_id($id) {
// cat
$cat = Category::join('catrelations', 'catrelations.idcat', '=', 'categories.id')->orderBy('categories.id', 'ASC')->get();
// menu
$nav = Nav::orderBy('index', 'DESC')->get();
$post = Post::find($id);
$setting = Settings::find(1);
$comments = Comment::where('post_id', '=', $id)->orderBy('id', 'asc')->get();
$get_reply = array();
$get_reply_id = array();
//$counter = 0;
//var_dump($comments);
foreach ($comments as $comment) {
$reply = Reply::where('parentId', '=', $comment->id)->get();
if (!$reply->isEmpty()) {
//$arr_reply[$comment->id] = $reply;
//echo $comment->id.' has reply!!!!!<br>';
//$reply_parent_id = $comment->id;
$counter = 0;
foreach ($reply as $replying) {
$get_reply[$comment->id][$counter] = Comment::where('id', '=', $replying->comment_id)->get();
//$comment_reply_id = $replying->comment_id;
//$reply_arr = array();
//$reply_arr[$comment->id] = $comment_reply_id;
//echo 'The reply is: '.$comment_reply_id.'<br>';
foreach ($get_reply[$comment->id][$counter] as $key => $value) {
//($value->text);
$get_reply_id[] = $value->id;
}
$counter++;
}
//$counter++;
}
}
$post_owner_info = User::select('id', 'first_name', 'last_name', 'image', 'desc')->find($post->user_id);
$arr = array();
$arr['comments'] = $comments;
$arr['post'] = $post;
//$arr['reply'] = $reply;
//var_dump($get_reply);
//var_dump($get_reply_id);
$new_post_inst = Post::where('id','=',$id)->first();
$new_post_inst->increment('visit');
return View::make('blogsingle', compact('arr'))->with('setting', $setting)->with('post', $post)->with('nav', $nav)->with('get_reply', $get_reply)->with('get_reply_id', $get_reply_id)->with('cat', $cat)->with('post_owner_info', $post_owner_info);
}
And this is my Post model:
class Post extends Eloquent
{
public function User()
{
return $this->belongsTo('User');
}
public function Categories()
{
return $this->belongstomany('Category');
}
public function comments()
{
return $this->hasMany('Comment');
}
}
you can try like this
$new_post_inst = Post::where('id','=',$id)->first();
$new_post_inst->increment('visit',1);
Try this:
$new_post_inst = Post::where('id','=',$id)->first();
$new_post_inst->visit+=1;
$new_post_inst->save();
OR this:
$new_post_inst = Post::where('id','=',$id)->first();
$new_post_inst->update(array('visit' => $new_post_inst->visit+1));
I searched around for a little bit and this could be the favicon icon that is making a second request. How did you declare the favicon? Or maybe you could remove it to try and see if it is indeed the favicon?