how to retrieve data from laravel relationship? - php

Hello im trying to do a quiz app with laravel and im struggeling with retrieving my questions with category..
i have a unsightly solution with this but it's not dynamically..
public function startQuiz(Request $request){
$questions = Question::all();
foreach ($questions as $key => $question) {
dd($question->where('categories_id', 'LIKE', 2)->get()); // i want to change the 2 to the real category id
}
}
i thought i could to that with the relationship like $question->category->id but wont work.
thats my model:
class Question extends Model
{
protected $fillable = ['question_text', 'categories_id', 'correct_answer', 'options'];
protected $casts = ['options' => 'array'];
public function category(){
return $this->belongsTo(Category::class, 'categories_id');
}
}
class Category extends Model
{
protected $fillable = ['name', 'slug'];
public function question()
{
return $this->hasMany(Question::class, 'questions_id');
}
i cant somehow pass the id and check it i dont know why.. thats my form where i pass the category:
#section('content-categories')
<div class="card">
<div class="card-header">Choose Category to Start</div>
<div class="card-body">
#foreach($categories as $category)
<form class="form-group" action="{{route('user.quiz', $category->slug)}}" method="post">
#csrf
<input type="submit" name="categoryTest" class="form-control" value="{{$category->name}}">
</form>
#endforeach
</div>
</div>
#endsection
Edit:
i think i know why i couldnt retrieve data with my relationship..i had an error with my relationship in Category Class, i have not a field for questions_id, i replaced it to:
public function question()
{
return $this->hasMany(Question::class);
}
and now i could get the questions with:
public function startQuiz(Request $request){
$questions = Question::all();
$questionsCategory = [];
if($request){
foreach ($questions as $question) {
if ($request->categoryTest == $question->category->name) {
$questionsCategory = $question->where('categories_id', '=', $question->category->id)
->get();
var_dump($questionsCategory);
}
}
}
}

For your form listing probably you already have $categories = Categories::all().
Next to get all questions of this category after the submit you should first get the category and then questions for it.
Let say you have category id in your form:
$categoryQuestions = Category::find($request->get('id'))->questions;
I see you are using category slug for your url, can also use it to find the category:
public function startQuiz(Request $request, string $slug){
$questions = Category::whereSlug($slug)->first()->questions;
...
}
Bonus:
In your service provider you can bind {category} directly to your model like this:
Route::bind('category', function($value) {
return Category::whereSlug('slug', $value)->first();
});
And then your category will be available in controller methods:
public function startQuiz(Request $request, Category $category){
$questions = $category->questions;
}

Try replacing 'LIKE' with '='. And remove dd(...)Like this:
foreach ($questions as $key => $question) {
dd($question->where('categories_id', 'LIKE', 2)->get());
}
to:
foreach ($questions as $key => $question) {
$question->where('categories_id', '=', 2)->get();
}

Related

Undefined Variable 'Comments' in Compact Laravel

I am getting 'undefined offset 1' error. It does returns everything I need -> the posts and the comments in the category. However, I believe the 'Undefined Offset 1' problem is probably due to some post which have no replies to them?? -> thus, help.
I have
1. Category Model
2. Post Model
3. Comment Model
This is the show function in my Category Model
public function show($id)
{
$category = Category::with('posts.comments')->find($id);
return view('categories.show', compact('category'));
}
I have made relation for 'Category hasMany Posts' -> 'Post hasMany Comments'.
You can try this
public function show($id)
{
$comments = []; // Empty array to initialize the variable. This variable will be filled once the foreach statement is ran.
$category = Category::find($id);
if($category !== null) {
$posts = $category->posts;
foreach($posts as $post) {
$comments = $post->comments;
}
}
return view('categories.show', compact('posts', 'category', 'comments'));
}
Alternative method
public function show(Category $category) //same as... public function show($id)
{
return view('categories.show', compact('category'));
/*
Render this content in the view.
#foreach($category->posts as $post)
{{-- Display Post Content --}}
#foreach($post->comments as $comment)
{{-- Display Comment Content --}}
#endforeach
#endforeach
*/
}
You are creating $comments variable inside foreach loop that make it's scope locally and will not be available outside foreach
To resolved this error you need to define your comments variable inside your function so that it is available in your function
public function show($id)
{
$comments = []; // define it here
$category = Category::find($id);
if($category !== null) {
$posts = $category->posts;
foreach($posts as $post) {
$comments = $post->comments;
}
}
return view('categories.show', compact('posts', 'category','comments'));
}

How to retrieve products of a nested category using Many-to-Many relationship in Laravel and order the result?

I want to retrieve products of the selected category, as well as sub-categories and I may even need to order them according to price or date. Please let me know what changes should be made to the controller!
I am using a Many to Many Relationship for the 2 tables: Categories and Products, and a category_product to relate both.
Example
(Not all categories have sub-categories)
Gifts
Books
Toys
Boys
Girls
Phone
Samsung
Nokia
If a user clicks Phone, all products of the category 'Phone', 'Samsung' or 'Nokia' should appear!
Database
Products: id, name, price, created_at
Categories: id, name, slug, parent_id, sorting_order, created_at
category_product: id, category_id, product_id
Code:
Category Model:
class Category extends Model
{
public function products()
{
return $this->belongsToMany('App\Product');
}
public function parent()
{
return $this->belongsTo('App\Category', 'parent_id');
}
public function children()
{
return $this->hasMany('App\Category', 'parent_id');
}
}
Product Model
class Product extends Model
{
public function categories()
{
return $this->belongsToMany('App\Category');
}
}
class ProductController extends Controller {
public function index($slug, Request $request)
{
if ( ! isset($_GET['sortBy']))
{
$category = Category::where('slug', $slug)->with('products')->first();
if ( ! $category)
{
abort(404, 'Page not found');
}
}
else
{
$slug = $request->segments()[1];
$products = Category::where('slug', $slug);
switch ($request->sortBy)
{
case 'latest':
$category = $products->with(['products' => function ($q) {
$q->orderBy('created_at', 'desc');
}])->first();
break;
case 'asc':
$category = $products->with(['products' => function ($q) {
$q->orderBy('price', 'asc');
}])->first();
break;
case 'desc':
$category = $products->with(['products' => function ($q) {
$q->orderBy('price', 'desc');
}])->first();
break;
default:
$category = $products->with('products')->first();
break;
}
}
return view('products', compact('category'));
}
}
View
<form id="sortProducts" class="form-inline" method="get">
{{ csrf_field() }}
<label for="sortBy">Sort By:</label>
<select name="sortBy" id="sortBy">
<option value="latest">Latest</option>
<option value="asc">Price Low to Hight</option>
<option value="desc">Price High to Low</option>
</select>
</form>
#foreach($category->products as $product)
<div class="product">
<img border="0" src="{{Voyager::image($product->image)}}" alt="{{$product->name}}">
<div class="product-name">{{$product->name}}</div>
<div class="product-price">${{$product->price}}</div>
</div>
#endforeach
I am using Laravel Version 6.2 along with Voyager Version 1.3.
If your category depth is unlimited, you'll need a recursive relationship.
I think something like this could work:
In your Category Model:
public function nestedStructure()
{
return $this->children()->with([
'nestedStructure',
'products' => function($q) {
$q->orderBy('created_at', 'desc');
}
);
}
Okay so first you need to modify your Category Model to get all children along with parent
Category Model
class Category extends Model
{
public function products()
{
return $this->belongsToMany('App\Product');
}
public function parent()
{
return $this->belongsTo('App\Category', 'parent_id');
}
public function children()
{
return $this->hasMany('App\Category', 'parent_id');
}
// recursive, loads all descendants
public function recursiveChildren()
{
return $this->children()->with('recursiveChildren');
}
}
Now according to given many-to-many relation a single Product might belong to multiple categories but we wont like to have same product show up again and again. So a possible fix for your Controller can be
class ProductController extends Controller {
public function index($slug, Request $request)
{
$categories = Category::where('slug', $slug)->with('recursiveChildren')->whereNull('parent')->get();
if($categories->count() == 0) abort(404, 'Page not found');
$products = collect([]); // its a helper method to make collections
foreach($categories as $category) {
$category_products = $category->products;
foreach($category_products as $product) {
if(!$products->contains($product)) $products->push($product);
}
}
if($request->input('soryBy')) {
switch ($request->sortBy)
{
case 'latest':
$products = $products->sortBy('created_at');
break;
case 'asc':
$products = $products->sortBy('price');
break;
case 'desc':
$products = $products->sortByDesc('price');
break;
default:
$products;
break;
}
}
return view('products', compact('products'));
}
}
Now lets modify the view a little
<form id="sortProducts" class="form-inline" method="get">
{{ csrf_field() }}
<label for="sortBy">Sort By:</label>
<select name="sortBy" id="sortBy">
<option value="latest">Latest</option>
<option value="asc">Price Low to Hight</option>
<option value="desc">Price High to Low</option>
</select>
</form>
#foreach($products as $product)
<div class="product">
<img border="0" src="{{Voyager::image($product->image)}}" alt="{{$product->name}}">
<div class="product-name">{{$product->name}}</div>
<div class="product-price">${{$product->price}}</div>
</div>
#endforeach
There are at least two solutions.
Solution 1 (Pure Laravel):
Add these two methods to your Category model:
public function descendants()
{
$collection = new \Illuminate\Support\Collection();
foreach ($this->children as $child) {
$collection->add($child);
$collection = $collection->merge($child->descendants());
}
return $collection;
}
public function getRouteKeyName()
{
return 'slug';
}
And use it in your ProductController controller like so:
class ProductController extends Controller
{
public function index(Request $request, Category $category)
{
$categories = $category->descendants()->add($category)->pluck('id');
$products = DB::table('category_product AS cp')
->join('products', 'cp.product_id', '=', 'products.id')
->select('products.*')
->whereIn('cp.category_id', $categories)
->get();
return view('products', compact('category', 'products'));
}
}
You can then output them in your view file:
#forelse($products as $product)
<div class="product">
<img border="0" src="{{ Voyager::image($product->image) }}" alt="{{ $product->name }}">
<div class="product-name">{{ $product->name }}</div>
<div class="product-price">${{ $product->price }}</div>
</div>
#empty
<div class="product">
There are no products in this category.
</div>
#endforelse
Solution 2 (Using a package):
First of all, install the package:
composer require kalnoy/nestedset
Replace parent_id column with $table->nestedSet(); in your categories table and related migration file:
Schema::create('categories', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('slug')->unique();
$table->nestedSet();
$table->timestamps();
});
Then, update your Category model like so:
use Illuminate\Database\Eloquent\Model;
use Kalnoy\Nestedset\NodeTrait;
class Category extends Model
{
use NodeTrait;
protected $fillable = [
'name',
'slug',
];
public function products()
{
return $this->belongsToMany('App\Product');
}
public function parent()
{
return $this->belongsTo(self::class, 'parent_id');
}
public function children()
{
return $this->hasMany(self::class, 'parent_id');
}
public function getRouteKeyName()
{
return 'slug';
}
}
You can now use it in your controller:
class ProductController extends Controller
{
public function index(Request $request, Category $category)
{
$categories = Category::descendantsAndSelf($category->id)->pluck('id');
$products = DB::table('category_product AS cp')
->join('products', 'cp.product_id', '=', 'products.id')
->select('products.*')
->whereIn('cp.category_id', $categories)
->get();
return view('products', compact('category', 'products'));
}
}
You can output as shown in Solution 1.
Please note that I assumed you use {category} key in your route definition. (See Route Model Binding) For example:
Route::get('/products/category/{category}', 'ProductController#index');
Read the documentation first to create, update and delete an item in a nested set (categories).

Trying to get property of non-object error in laravel

ive been trying to match User_id from my users with Creator_id to display the creators username in my post
but im getting the trying to get property from non-object error and i cant get arround it
the function in my controller
postcontroller.php:
public function index()
{
$allusers = users::getusers();
$posts = DB::table('posts')->get();
foreach ($posts as $posts) {
foreach ($allusers as $allusers) {
if ($posts->creator_id == $allusers->user_id) {
array_push($posts,$allusers->username);
}
}
}
return view('blog',['posts'=>$posts]);
}
the functions in my model:
public static function getusers(){
$allusers = users::all();
return $allusers;
}
You are using the same variable names in your foreach loops :
foreach ($posts as $post) {
foreach ($allusers as $user) {
if ($post->creator_id == $user->user_id) {
array_push($post,$user->username);
}
}
}
But for your issue, you should try to add this in your Post Model
public function user(){
return $this->belongsTo(User::class, 'creator_id', 'user_id);
}
then in your view :
#foreach($posts as $post)
{{ $post->user->name }}
#endforeach
https://laravel.com/docs/5.4/eloquent-relationships#one-to-many-inverse
Does your users table has a column like user_id?
I think you have check with users table id with posts creator_id.

Laravel belongsTo returns, put can't save in var

I'm trying to get the username by a item with belongsTo.
I have this in my Item model:
public function user()
{
return $this->belongsTo('User', 'user_id', 'id');
}
And this in my controller:
$user = $item->user->username;
But I get:
Trying to get property of non-object
And when I do:
return $item->user->username;
In my controller it works.
What can be wrong?
Controller/Model: http://pastebin.com/qpAh8eFd
You have following function in your controller:
public function index($type)
{
$items = $this->item->where('type', '=', $type)->get();
foreach($items as $item):
$user = $item->user->username;
endforeach;
return View::make('items.index', ['items' => $items, 'user' => $user]);
}
You don't need to do a foreach look in the controller and whatever you are doing you are doing it wrong, instead make your index function like this:
public function index($type)
{
$items = $this->item->where('type', '=', $type)->get();
return View::make('items.index', ['items' => $items]);
}
Pass only $items to your items/index.blade.php view. You may do a foreach loop in your view if needed and within each iteration you may access the user related to the item using something like this:
#foreach($items as $item)
{{ $item->user->uername }}
#endforeach
You may get Trying to get property of non-object error message because each $item may don't have a related user. So, make sure each $item has a related user. You can also, use following to get items with user:
$items = $this->item->with('user')->where('type', '=', $type)->get();
return View::make('items.index', ['items' => $items]);
In your view you may check if the item has a related user:
#foreach($items as $item)
#if($item->user)
{{ $item->user->uername }}
#endif
#endforeach

Retrieve Related Models using Laravel 4

I have 3 tables: Users, Projects, Items and Friends. I want to get all the items for a project and list each item with related friends. Am I missing something in my model? Ultimately I want to get all the friends which are related to the items.
// CONTROLLER
public function show($id)
{
$uid = Auth::user()->id;
$projects = Project::find($id)->with('item.friend')->get();
return View::make('projects.show', compact('projects'));
}
//VIEW
#foreach ($projects as $project)
#foreach ($project->friend as $friend)
<li>
<a href="#" class='itemLink' >{{$friend->email}}</a>
<a href="#" class='itemLink' >{{$projects->item->name}}</a>
</li>
#endforeach
#endforeach
// MODELS
class User extends Eloquent {
public function project()
{
return $this->hasMany('Project');
}
public function item()
{
return $this->hasMany('Item');
}
public function friend()
{
return $this->hasMany('Friend');
}
class Project extends Eloquent {
protected $guarded = array();
public static $rules = array();
public function item()
{
return $this->hasMany('Item');
}
public function friend()
{
return $this->hasMany('Friend');
}
class Item extends Eloquent {
protected $guarded = array();
public static $rules = array();
public function friend()
{
return $this->hasMany('Friend');
}
class Friend extends Eloquent {
protected $guarded = array();
public static $rules = array();
You are missing a loop. I recommend that when setting up a many to many relationship, make sure your method is plural. It makes a lot more sense when trying to read the code. You'd then send up with #foreach $project->items as $item or $item->friends as $friend.
#foreach ($projects as $project)
#foreach ($project->item as $item)
#foreach($item->friend as $friend)
<li>
<a href="#" class='itemLink' >{{$friend->email}}</a>
<a href="#" class='itemLink' >{{$item->name}}</a>
</li>
#endforeach
#endforeach
#endforeach
Your models seems OK at first look. I think since you are pulling item.friend relationship, this line
#foreach ($project->friend as $friend)
should be,
#foreach ($project->item->friend as $friend)

Categories