Null Relation in Laravel Model - laravel

These are my Migrations
Type Migration
Schema::create('digital_types', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
Content Migration
Schema::create('digital_products', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('product_type_id')->nullable();
$table->string('name');
$table->unsignedTinyInteger('status')->default(1);
$table->softDeletes();
$table->timestamps();
$table->foreign('product_type_id')->references('id')->on('digital_types')->nullOnDelete()->cascadeOnUpdate();
});
Models defined:
Type Model
class DigitalType extends Model
{
use HasFactory;
public function digitalContents() {
return $this->hasMany(DigitalProduct::class);
}
}
Content Model
class DigitalProduct extends Model
{
use HasFactory;
public function digitalContentType() {
return $this->belongsTo(DigitalType::class);
}
public function categories(){
return $this->belongsToMany(Category::class, 'digital_product_category');
}
}
But when I want to grab my Content with Type Relation by with method, It returns NULL.
My Controller
class DigitalProductController extends Controller
{
public function productsList(){
$products= DigitalProduct::with('digitalContentType')->get();
echo $products;
// return view('pages.digitalproducts', compact('products'));
}
}
and The data controller echo in browser is null (end of these two lines)
[{"id":1,"product_type_id":1,"name":"deserunt","description":"Id nam amet voluptatibus quia.","image_url":null,"content_url":null,"price":"3.00","discount":"7.00","status":1,"deleted_at":null,"created_at":"2021-12-29T13:47:41.000000Z","updated_at":"2021-12-29T13:47:41.000000Z","digital_content_type":null},
{"id":2,"product_type_id":3,"name":"aut","description":"Saepe ratione soluta aspernatur aspernatur debitis dolor.","image_url":null,"content_url":null,"price":"8.00","discount":"7.00","status":1,"deleted_at":null,"created_at":"2021-12-29T13:47:41.000000Z","updated_at":"2021-12-29T13:47:41.000000Z","digital_content_type":null},
And another thing that my Database populated with fake data, for both Content and Type
+----+------------+------------+------------+
| id | name | created_at | updated_at |
+----+------------+------------+------------+
| 1 | ebook | NULL | NULL |
| 2 | audio book | NULL | NULL |
| 3 | magazin | NULL | NULL |
| 4 | news paper | NULL | NULL |
+----+------------+------------+------------+
+----+-----------------+------------+----------------------------------------------------------------+-----------+-------------+-------+----------+--------+------------+---------------------+---------------------+
| id | product_type_id | name | description | image_url | content_url | price | discount | status | deleted_at | created_at | updated_at |
+----+-----------------+------------+----------------------------------------------------------------+-----------+-------------+-------+----------+--------+------------+---------------------+---------------------+
| 1 | 1 | deserunt | Id nam amet voluptatibus quia. | NULL | NULL | 3.00 | 7.00 | 1 | NULL | 2021-12-29 13:47:41 | 2021-12-29 13:47:41 |
| 2 | 3 | aut | Saepe ratione soluta aspernatur aspernatur debitis dolor. | NULL | NULL | 8.00 | 7.00 | 1 | NULL | 2021-12-29 13:47:41 | 2021-12-29 13:47:41 |

I think problem is that the foreign ID column in digital_products does not have name by Laravel standards.
If the column name is not by Laravel standard, you have to specify it in realtionship method:
public function digitalContentType() {
return $this->belongsTo(DigitalType::class, 'product_type_id');
}

Related

Laravel with() eager loading returning empty data

I have a one-to-many relationship in my model. Basically a Category and a Product. A product can only have one category but a category can have many products. The code below works:
return Category::select('id', 'name')->whereIn('id', $categories)->with('products')->get();
It returns with a product key and within that the product columns in the database, but when I use eager loading it just returns an empty set:
return Category::select('id', 'name')->whereIn('id', $categories)->with(['products' => function($query){
$query->limit(5);
}])->get();
I've also tried adding the return keyword like this return $query->limit(5); but still no luck.
I have also tried specifying columns like this:
return Category::select('id', 'name')->whereIn('id', $categories)->with('products:id,name')->get();
But it still returns an empty dataset.
Since I'm building an API, this is what the JSON data looks like:
[
{
"id": 161,
"name": "Health & Personal Care",
"products": []
},
{
"id": 256,
"name": "Makeup & Fragrances",
"products": []
},
]
My table structure:
categories (there's no product_id column, since it's one to many)
+----+------+
| id | name |
+----+------+
| | |
+----+------+
| | |
+----+------+
| | |
+----+------+
product
+----+------+-------+-------------+
| id | name | price | category_id |
+----+------+-------+-------------+
| | | | |
+----+------+-------+-------------+
| | | | |
+----+------+-------+-------------+
| | | | |
+----+------+-------+-------------+
My category model is declared like this:
public function products()
{
return $this->hasMany(Product::class);
}
and the product model is:
public function category()
{
return $this->belongsTo(Category::class);
}
you are trying to limit the loaded relation, not the query, you can do this using eloquent-eager-limit
install it:
composer require staudenmeir/eloquent-eager-limit:"^1.0"
then in Category Model:
class Category extends Model
{
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
.....
public function products()
{
return $this->hasMany(Product::class, 'product_id');
}
public function lastFiveProducts()
{
return $this->hasMany(Product::class, 'product_id')
->latest()->limit(5);
}
}
and in Product:
class Product extends Model
{
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
......
}
now this query will get the expected results:
return Category::select('id', 'name')->whereIn('id', $categories)->with(['products' => function($query){
$query->limit(5);
}])->get();
or using the new relation:
return Category::select('id', 'name')->whereIn('id', $categories)->with(['lastFiveProducts'])->get();
also note when you use ->with('products:id,name') loading a relation with specific columns, you always should load the foreing key ->with('products:id,name,category_id')

Laravel QueryBuilder to Eloquent Relationship

Updated:
I'm almost getting my result, I'd just like to filter the "current" student I'm looking for
My updated model:
// Student.php
public function enrolled()
{
return $this->belongsToMany('App\Classroom', 'inscribed_students')
->with('enrolled_steps.student_process')
->whereRaw('start_at >= curdate()');
}
// Classroom.php
public function enrolled_steps()
{
return $this->hasMany('App\InscribedStudent');
}
// InscribedStudent.php
public function student_process()
{
return $this->hasMany('App\StudentSelectionProcess');
}
My output json:
{
"id": 1,
"name": "This is a Student",
"email": "dborer#example.org",
"enrolled": [
{
"id": 31,
"name": "This is a Classroom",
"shift": "Morning",
"enrolled_steps: [
{
"id": 1,
"student_id": 1,
"classroom_id": 1,
"student_process": [
{
"id": 1,
"status": "Approved"
}
]
},
{
"id": 2,
"student_id": 2,
"classroom_id": 1,
"student_process": [
{
"id": 2,
"status": "Approved"
}
]
},
]
}
]
}
The current problem is the enrolled_steps are return a array, but im filter one student, how can I fix it to get only my current student?
My expected output:
{
"id": 1,
"name": "This is a Student",
"email": "dborer#example.org",
"enrolled": [
{
"id": 31,
"name": "This is a Classroom",
"shift": "Morning",
"enrolled_steps: {
"id": 1,
"student_id": 1,
"classroom_id": 1,
"student_process": [
{
"id": 1,
"status": "Approved"
}
]
}
}
]
}
Question:
My problem is I have to do multiple/hard relationship to show info about student.
+--------------------+
| Student |
+--------------------+
| id |
| name |
| email |
+--------------------+
+--------------------+
| Classroom |
+--------------------+
| id |
| name |
| shift |
+--------------------+
+--------------------+
| InscribedStudent |
+--------------------+
| id |
| student_id | << Foreign key
| classroom_id | << Foreign key
+--------------------+
+--------------------+
| SelectionProcess |
+--------------------+
| id |
| classroom_id | << Foreign key
| enabled |
+--------------------+
+-------------------------+
| StudentSelectionProcess |
+-------------------------+
| id |
| inscribed_student_id | << Foreign key
| selection_process_id | << Foreign key
| status |
+-------------------------+
My QueryBuilder
$student = DB::table('students')
->join('inscribed_students', 'inscribed_students.student_id', '=', 'students.id')
->join('classrooms', 'classrooms.id', '=', 'inscribed_students.classroom_id')
->join('selection_processes', 'selection_processes.classroom_id', '=', 'classrooms.id')
// If exists show, else null
->leftjoin('student_selection_processes', 'student_selection_processes.selection_process_id', '=', 'selection_processes.id')
->select('students.*', 'classrooms.*', 'student_selection_processes.*')
->where([
['selection_processes.enabled', 1], // Very important
['students.id', $id]
])
->first();
But I consider the way very messy and still need to rearrange for the resource, so I want know if is possible to convert this Query to Eloquent relationship.
My expected eloquent json result
{
"id": 1,
"name": "This is a Student",
"email": "dborer#example.org",
"enrolled": [
{
"id": 31,
"name": "This is a Classroom",
"shift": "Morning",
"process: {
"id": 5,
"status": "Approved"
}
}
]
}
I can get at the classrooms but I don't know how can I get the process
// StudentController.php - Student controller
$student = Student::with(['enrolled'])
->find($id);
// Student.php - Student Model
public function enrolled()
{
return $this->belongsToMany('App\Classroom', 'inscribed_students');
}
If possible, if student_selection_processes is null don't show the related classroom
I am not fully understanding what you are after, however, something like this might help:
Student::with(['classroom', 'classroom.selectionProcess', 'classroom.selectionProcess.StudentSelectionProcess'])
->having(['classroom.selectionProcess.StudentSelectionProcess'])
->find($id);
Have a look at nested eager loading in Laravel Docs: https://laravel.com/docs/5.8/eloquent-relationships#eager-loading
Model Student(table name)
//relation beetween student and classroom
public function StudentClassroom(){
return $this->belongsToMany('App\Classroom','inscribed_student','student_id','classroom_id')->withTimestamps();
}
//relation beetween Student and InscribedStudent
public function enrolled(){
return $this->hasMany('App\InscribedStudent','student_id','id');
}
Model Classroom (table name)
//relation beetween classroom and student
public function classroomstudents(){
return $this->belongsToMany('App\Student','inscribed_student','classroom_id','student_id')->withTimestamps();
}
//relation beetween classroom and SelectionProcess
public function selectionclass(){
return $this->hasMany('App\SelectionProcess','classroom_id','id');
}
Model SelectionProcess(table name)
//relation beetween SelectionProcess and InscribedStudent
public function SelectionProcesInscribedStudent(){
return $this->belongsToMany('App\InscribedStudent','student_selection_process ','inscribed_student_id','selection_process_id')->withTimestamps();
}
Model InscribedStudent(table name)
//relation beetween InscribedStudent and SelectionProcess
public function InscribedStudentSelectionProces(){
return $this->belongsToMany('App\SelectionProcess','student_selection_process ','selection_process_id','inscribed_student_id')->withTimestamps();
}
//relation beetween InscribedStudent and Student
public function student()
{
return $this->belongsTo('App\Student','student_id','id');
}
//relation beetween InscribedStudent and classroom
public function classroom()
{
return $this->belongsTo('App\classroom','classroom_id','id');
}
Model StudentSelectionProcess(table name)
//relation beetween StudentSelectionProcess and InscribedStudent
public function inscribed_student()
{
return $this->belongsTo('App\InscribedStudent','inscribed_student_id','id');
}
//relation beetween StudentSelectionProcess and SelectionProcess
public function selection_process()
{
return $this->belongsTo('App\SelectionProcess','selection_process_id','id');
}
Now do in your controller
Student::with(['StudentClassroom','enrolled.InscribedStudentSelectionProces']);
Take a look on this : https://laravel.com/docs/5.8/eloquent-relationships
It's how I would do this. Rename the relationship to the one you will create in your model.
$student = Student::with(['manyToMany_classrooms_students.hasMany_selectionProcess' => function($query1) use($id) {
$query1->where('enabled', 1);
$query1->with(['hasMany_studentSelectionProcess' => function($query2) use($id) {
$query2->where('student_id', $id);
}])
}])->find($id);
I would also change your table for this
+--------------------+
| InscribedStudent |
+--------------------+
| //id | << Remove id -> you don't need this for that situation
| student_id |
| classroom_id |
+--------------------+
+-------------------------+
| StudentSelectionProcess |
+-------------------------+
| id |
| student_id | << Refer directly to your student -> InscribedStudent is
| selection_process_id | not a model, it's a pivot table
| status |
+-------------------------+
Using this, with correct relationship, you can find a user with StudentSelectionProcess. Join the SelectionProcess then the classroom.
Student -> hasMany -> StudentSelectionProcess -> belongsTo -> SelectionProcess -> belongsTo -> Classroom
Taking time to create relationships correctly will make your life easier.

Overriding default names of Laravel timestamps not having any effect

I am trying to override the default names of the Laravel timestamps on a particular table, but it seems to be having no effect at all.
I have updated my model with the following:
/**
* The name of the "created_at" column.
*
* #var string
*/
const CREATED_AT = 'CreatedAt';
/**
* The name of the "updated_at" column.
*
* #var string
*/
const UPDATED_AT = 'UpdatedAt';
Updated the migration:
...
$table->integer( 'UpdatedBy' )->nullable();
$table->timestamps();
});
I run the migration and check the results in my database, and find that the fields have not changed:
...
| UpdatedBy | int(11) | YES | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+-----------------------+------------------+------+-----+-----------------------------------------+----------------+
Have I missed something maybe?
If you check Illuminate\Database\Schema\Blueprint you must be see that.
public function timestamps()
{
$this->timestamp('created_at')->nullable();
$this->timestamp('updated_at')->nullable();
}
If you wont to change your created_at, and updated_at columns then you can use
...
$table->integer( 'UpdatedBy' )->nullable();
$table->timestamp(CorrespondModel::CREATED_AT)->nullable();
$table->timestamp(CorrespondModel::UPDATED_AT)->nullable();
});
or directly create new names
...
$table->integer( 'UpdatedBy' )->nullable();
$table->timestamp('CreatedAt')->nullable();
$table->timestamp('UpdatedAt')->nullable();
});

Laravel ordering a model belongsTo

This is my models structure:
class Family extends Model
{
public function members()
{
return $this->hasMany('App\Person');
}
}
class Person extends Model
{
public function family()
{
return $this->belongsTo('App\Family');
}
public function school()
{
return $this->belongsTo('App\School');
}
}
class School extends Model
{
public function students()
{
return $this->hasMany('App\Person');
}
}
In short, a Person belongs to many Schools, but only one Family.
Dummy data:
families_table
id | name
---------
1 | Smith
2 | Doe
people_table
id | family_id | school_id | name
----------------------------------
1 | 1 | 1 | Betty
2 | 2 | 1 | John
school_table
id | name
-------------
1 | MIT
So we have to users: Smith, Betty and Doe, John. Now, if I do this:
$school = School::find(1);
foreach ($school->students AS $student) {
echo $student->family->name . ', ' . $student->name
}
I will get:
Smith, Betty
Doe, John
What I want to see is:
Doe, John
Smith, Betty
How do I get the School and list the members sorted by the name field in from the Family table?
The easiest way is by sorting collection:
$school->students->load('family');
$school->students->sortBy(function ($item) {
return $item->family->name;
});
You can also sort results in query using join like:
$students = $school->students()
->with('family')
->select('students.*')
->join('families', 'families.id' , '=', 'students.family_id')
->orderBy('families.name')
->get();
$school->setRelation('students', $students);

Laravel - How to get data like hasOne in a many-to-many relationship

Here are my tables having a many-to-many relationship and group_user as the intermediate table.
| users | groups | group_user |
-------------------------------------------
| id | id | group_id |
| name | name | user_id |
| created_at| creator_id | created_at |
| updated_at| created_at | updated_at |
| | updated_at | |
groups.creator is the user id referenced from users table.
When I try to query the group table, it returns something like this.
[{"id":1,"name":"Akatsuki","creator":1,"created_at":"2015-10-13 12:22:20","updated_at":"2015-10-13 12:22:20"}]
What I want is when I query the groups table, it also returns the user data of the creator like hasOne relationships do.
In User Model
public function groups()
{
return $this->belongsToMany('App\Group')->withTimestamps();
}
In Group Model
public function users()
{
return $this->belongsToMany('App\User')->withTimestamps();
}
After I query the groups, I just overwrite the group_creator column.
foreach($available as $data) {
$data['group_creator'] = User::find($data['group_creator']);
}
Now it returns
[{"id":1,"group_name":"Akatsuki","creator":{"id":1,"firstname":"John","lastname":"Doe","created_at":"2015-10-13 12:19:35","updated_at":"2015-10-13 14:14:41"},"created_at":"2015-10-13 12:22:20","updated_at":"2015-10-13 12:22:20"}]
Along with the user details.

Resources