laravel eloquent string field for Primary Key goes data error - laravel

I guess it is too much simple but goes query result error .
I made Country table , models.
I just want it query only .
The only point not usual is that I did not use 'id' field for primary field.
I used 'cc' field for primary .
That's it. but it goes response data error .
in migration file
Schema::create('countries', function (Blueprint $table) {
$table->string('cc')->index()->unique();
$table->string('name_eng');
$table->primary('cc');
});
in Controller
function run() {
$countries = Country::OrderBy('cc', 'asc')->take(3)->get();
$data['countries'] = $countries;
return response()->json( $data, 200);
}
in Country Model
class Country extends Model
{
protected $table='countries';
protected $primaryKey = 'cc';
public $timestamps = false;
protected $fillable = ['cc', 'name_eng'];
}
in result
{
"countries": [
{
"cc": 0, --> it should be 'ad'
"name_eng": "Andorra",
},
{
"cc": 0, --> it should be 'ae'
"name_eng": "United Arab Emirates",
},
{
"cc": 0, --> it should be 'af'
"name_eng": "Afghanistan",
}
]
}
in backup sql
INSERT INTO `countries` (`cc`, `name_eng`)
VALUES
('ad', 'Andorra'),
('ae', 'United Arab Emirates'),
('af', 'Afghanistan');
Why Do I get lost 'cc' field values ?
I dont understand where the wrong comes from.
somebody can help me ?

In your model write public $incrementing = false
So model will understand that your primary key is string

Related

Laravel - for some reason, max breaks my query

I have this piece of code:
$query = Student::whereHas('statusuri', function($q) use ($status) {
$q->latest('status_student.id')->take(1)
->where('status_id', $status)
->whereNotNull('status_id');
});
and it works fine, but I don't necessarily get the desired result.
I tried changing the first line to max (so I don't filter all my records then do a limit 1), I just get the highest ID from the beginning - like this:
$query = Student::whereHas('statusuri', function($q) use ($status) {
$q->max('status_student.id')
->where('status_id', $status)
->whereNotNull('status_id');
});
but then my query breaks.
For some reason, I get this:
Unknown column 'students.id' in 'where clause' (SQL: select max(`status_student`.`id`) as aggregate from `statusuri` inner join `status_student` on `statusuri`.`id` = `status_student`.`status_id` where `students`.`id` = `status_student`.`student_id`)
Why does my query break after I do this change?
Thanks.
Tables:
students
id bigint(20)
//other non-related data
statusuri
id bigint(20)
nume VARCHAR(255)
status_student
id int(11)
student_id int(10)
status_id int(10)
stare_stagiu_id int(11)
created_at timestamp
updated_at timestamp
statusuri() from Student
public function statusuri()
{
return $this->belongsToMany(Status::class, 'status_student')
->withPivot('id', 'data_inceput', 'data_sfarsit', 'document', 'status_id', 'stare_stagiu_id')
->withTimestamps();
}
Status and StatusStudent classes
class StatusStudent extends Model
{
protected $table = 'status_student';
protected $fillable = ['id', 'student_id', 'status_id', 'stare_stagiu_id'];
}
class Status extends Model
{
protected $table = 'statusuri';
public $fillable = ['nume'];
}
Your relations are messed up. The query is trying to use a column from the student table, but the student table is not available in said query as it is not joined. See this fiddle to see what goes wrong in the SQL.
http://sqlfiddle.com/#!9/52c96fa/6
In the end, I'd do it like this if I understood you correctly:
In StatusStudent.php (Model):
public function student() {
return $this->hasOne(Student::class, 'id', 'student_id');
}
In Controller:
public function stackoverflowtest() {
//Set teststatus
$status = 1;
//Get the latest status of all users - and if that status is correct, retrieve into array
$latest = DB::select( DB::raw("SELECT max(id) as id, student_id FROM status_student group by student_id"));
$array = [];
foreach ($latest as $l) {
$status_id = StatusStudent::whereId($l->id)->whereStatusId($status)->first();
if ($status_id) {
array_push($array, $status_id);
}
}
//$array now holds all the StatusStudent, simply user ->student to get the student related to said status, example below
if($array) {
dd($array[0]->student);
return $array;
} else {
return 'No match';
}
}
First, we get all latest records for each user IF the status is correct. Then, we simply get the Student from the status_student table via the relation.

How to select custom attributes fields with relation in Laravel?

I have tables emails and email_attachments as follows:
Schema::create('emails', function (Blueprint $table) {
$table->id();
$table->string('email')->nullable(false);
$table->string('subject')->nullable(false);
$table->text('body')->nullable(false);
$table->timestamps();
});
Schema::create('email_attachments', function (Blueprint $table) {
$table->id();
$table->foreignId('email_id')->nullable(false);
$table->string('file_name')->nullable(false);
$table->string('file_path')->nullable(false);
$table->foreign('email_id')->references('id')->on('emails');
});
And my models classes Email and EmailAttachment as follows:
class Email extends Model
{
use HasFactory;
protected $fillable = ['email', 'subject', 'body'];
function attachments()
{
return $this->hasMany(EmailAttachment::class);
}
}
class EmailAttachment extends Model
{
use HasFactory;
public $timestamps = false;
protected $fillable = ['email_id', 'file_name', 'file_path'];
protected $appends = ['url'];
function email()
{
return $this->belongsTo(Email::class);
}
public function getUrlAttribute()
{
return Storage::disk('public')->url($this->file_name);
}
}
Doing this return Email::with('attachments')->get(); I get everything as response:
{
"id": 1,
"email": "me#me.com",
"subject": "My Subject",
"body": "<html><body><h1>My test message.</h1></body></html>",
"created_at": "2021-02-26T23:32:08.000000Z",
"updated_at": "2021-02-26T23:32:08.000000Z",
"attachments": [
{
"id": 1,
"email_id": 1,
"file_name": "foo.jpg",
"file_path": "/home/user/laravel/storage/app/foo.jpg",
"url": "http://127.0.0.1:8000/storage/foo.jpg"
}
]
}
And this return Email::with('attachments:file_name')->get(); I get attachments as an empty array:
{
"id": 1,
"email": "me#me.com",
"subject": "My Subject",
"body": "<html><body><h1>My test message.</h1></body></html>",
"created_at": "2021-02-26T23:32:08.000000Z",
"updated_at": "2021-02-26T23:32:08.000000Z",
"attachments": []
}
How can I get only this?
{
"email": "me#me.com",
"subject": "My Subject",
"body": "<html><body><h1>My test message.</h1></body></html>",
"attachments": [
{
"url": "http://127.0.0.1:8000/storage/foo.jpg"
}
]
}
I already did this tries:
return Email::with('attachments:url')->get(['email', 'subject', 'body']);
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'url' in 'field list' (SQL: select `url` from `email_attachments` where `email_attachments`.`email_id` in (0))"
return Email::with('attachments')->get(['email', 'subject', 'body', 'url']);
"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'url' in 'field list' (SQL: select `email`, `subject`, `body`, `url` from `emails`)"
return Email::with('attachments:url')->get(['email', 'subject', 'body', 'url']);
"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'url' in 'field list' (SQL: select `email`, `subject`, `body`, `url` from `emails`)"
PS.: I'm using Laravel 8.29.0.
There are several ways you can achieve this, usually, in other frameworks, these objects tend to be called "DTO objects" ( Data Transfer Objects )
For Laravel, you could simply get the resulting query from the database, and perform simple array transformations on it using the collections() API.
For example:
$collection = Email::with('attachments')->get();
$collection->map(function ($record) {
return ['email' => $record->email, 'subject' => $record->subject, 'attachments' => $record->attachments ];
});
Now you would just return this as the response:
$response = $collection->map(...);
return response()->json($response, 200);
From your controller.
Though you should be aware that these transformations happen in PHP and not SQL side of things, but the user won't know which field you've returned from the database. https://laravel.com/docs/8.x/queries#select-statements
You can add ->select() anywhere between the other queries and ->get() because until that point you're still in the query builder mode ( You're making your SQL query )
So you could do this:
Email::with('attachments')->select('my_field','my_field2')->get();
Keep in mind that this works for any query builder methods. https://laravel.com/docs/8.x/queries
Finally, you may look into API resources, as they provide a way you can format your responses. The documentation will explain it with far more better job then me: https://laravel.com/docs/8.x/eloquent-resources
When loading specific fields in eager loading you should also select the identifier of the record which is used to map the related records. So in your case email (hasMany) attachments via foreign key email_id which points to Email
See Eager Loading Specific Columns
When using this feature, you should always include the id column and any relevant foreign key columns in the list of columns you wish to retrieve.
Email::with('attachments:email_id,file_name')->get();
Now this will give you 2 fields from attachments relation
"attachments": [
{
"email_id": 1,
"file_name": "foo.jpg"
}
]
So if you need url attribute which is a custom attribute and not physically present in your table you will need to trick the query builder to produce such SQL query with url attribute included, initially with null value and later on when this query output is transformed by laravel data transformation your url's accessor getUrlAttribute() will fill this attribute with your expected output
Email::with(['attachments' => function ($query) {
$query->select(['email_id', 'file_name', \DB::raw('null as url')]);
}])->get();
Now attachments collection will have following data in it
"attachments": [
{
email_id": 1,
"file_name": "foo.jpg",
"url": "http://127.0.0.1:8000/storage/foo.jpg"
}
]

How to use Accessor as a condition in Laravel?

I have this Accessor bellow which returns one string result (example: 'Ready' or 'Ongoing' ...):
public function getMinimumStatusAttribute()
{
$statusIds = [];
$tasks = $this->tasks()->get();
foreach ($tasks as $task) {
array_push($statusIds, $task->task_status_id);
}
return taskStatus::orderBy('order', 'asc')
->where('operational_status', '=', true)
->whereIn('id', $statusIds)->value('name');
}
I have TeamleaderDeal model :
return TeamleaderDeal::all();
which returns:
[
{
"id": 4,
"teamleader_id": "8b03da5a-af1f-051d-ad6d-b6199c7f7c35",
"created_at": "2019-09-09 11:41:46",
"updated_at": "2019-09-14 20:57:03",
"title": "Brand identity #2",
"reference": "12",
"lead_company_id": "adeddc13-6962-0a19-ac72-6713d1cf1455",
"teamleader_company_id": 1,
"dealphase_id": "199a34fc-de5c-038d-a655-c61fa4d97d17",
"estimated_closing_date": null,
"po_number": "az215487",
"teamleader_deal_phase_id": 6,
"dealPhase": "Refused",
"tasksCount": 5,
"companyLanguage": "nl",
-------------------------
"minimumStatus": "ready", ||||||accessor results
-------------------------
"invoiced": 1,
(...)
I would like to filter TeamleaderDeal results by MinimumStatus to show for example only results where MinimumStatus equal to 'Ready' ?
Thanks
You may use
public function getMinimumStatusAttribute($minimumStatus = NULL) {
if($minimumStatus === 'Ready') {
// do the magic here
$statusIds = [];
$tasks = $this->tasks()->get();
foreach ($tasks as $task) {
array_push($statusIds, $task->task_status_id);
}
return taskStatus::orderBy('order', 'asc')
->where('operational_status', '=', true)
->whereIn('id', $statusIds)->value('name');
}
// else return default value
return $this->attributes['minimum_status'];
}
Edit
Regarding your updates you may use reject method provider by Laravel Collection
E.g
$teamleaderDeals = TeamleaderDeal::all();
return $teamleaderDeals->reject(function ($teamleaderDeal) {
return $teamleaderDeal->minimumStatus !== 'Ready';
});
I think you need this simple where clause:
return taskStatus::orderBy('order', 'asc')
->where('operational_status', '=', true)
->where('name', 'Ready')
->whereIn('id', $statusIds)->value('name');
What are accessors?
Accessors create a dummy attribute on the object which you can access as if it were a database column. So if your database has user table and it has FirstName and LastName column and you need to get full name then it will be like.
Syntax of accessors:
get{Attribute}Attribute
Example :
public function getFullNameAttribute(){ return $this->FirstName. " ".$this->LastName;}
After that you can get full user name with below accessors.
{{ $user->full_name }}
After that you can get full user name with below accessors.
{{ $user->full_name }}
What is Mutator ?
Mutator is use to set value of attribute, a mutator transforms an eloquent attribute value when it is set.
How to define a mutator,
set{Attribute}Attribute
Above method on your model where {Attribute} is the "studly" cased name of the column you wish to access.
Example :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Register extends Model
{
/**
* Set the user's first name.
*
* #param string $value
* #return void
*/
public function setNameAttribute($value)
{
$this->attributes['name'] = strtolower($value);
}
}
Now you can use this in your controller like.
use App\Models\Register;
$register= Register::find(1);
$register->name = 'Websolutionstuff';

Eloquent Eager Loading with $append Attribute

I'm kinda stuck with this here and don't know how to move forward with this one.
I have two Models: user and child and they are in a Relationship.
( Keep in mind that this only illustrate the problem )
class Child extends Model{
protected $primaryKey = 'id_child';
public $appends = ['is_alive'];
public function user(){
return $this->belongsTo('Models\User','id_user');
}
public function getIsAliveAttribute(){
if (!is_null($this->lifetime_updated_at))
return (Carbon::parse($this->lifetime_updated_at)->addMinute($this->lifetime) >= Carbon::now());
else
return false;
}
}
class User extends Model{
protected $primaryKey = 'id_user';
public $appends = ['is_alive'];
public function childs(){
return $this->hasMany('Models\Child','id_user');
}
public function getIsAliveAttribute(){
if (!is_null($this->lifetime_updated_at))
return (Carbon::parse($this->lifetime_updated_at)->addMinute($this->lifetime) >= Carbon::now());
else
return false;
}
}
Now I want to use Eager Loading in the Controller to retrieve my Childs data from User.
But my User Model Object comes from an MiddleWare in my Application. So I only have the User Model Object to use and I don't want to Query the User again using "with()".
$user = User::where('name','DoctorWho')->first();
return user->childs()->find(3);
What this operation returns:
{
"id_child": 3,
"name": "JayZ",
"last_name": "Etc",
"lifetime": 1,
"lifetime_updated_at": null,
"created_at": "2017-05-29 21:40:02",
"updated_at": "2017-05-29 21:40:02",
"active": 1
}
What I needed ( With Attribute Appended)
{
"id_child": 3,
"name": "JayZ",
"last_name": "Etc",
"lifetime": 1,
"lifetime_updated_at": null,
"created_at": "2017-05-29 21:40:02",
"updated_at": "2017-05-29 21:40:02",
"active": 1,
"is_alive": true
}
Is even possible to retrieve Child Data with Appended Attributes using Eager Loading ?
Notes: This user object come from an Middleware, I must use the User Model Object to get its child's with the appended attribute.
Thanks in Advance,
LosLobos
The thing is I was doing something wrong that is not related to Eloquent acessing the model like that does come with the appended attributes!
What you're doing here is not eager loading. Your childs relationship is wrong. Should be return $this->hasMany('Models\Child', 'id_user'); based on the model and the other relationship defined.
Here are some ways you can access the child information. These by default should respect the $appends property and load the field.
$childId = 3;
$user = User::with('childs')
->where('name', 'DoctorWho')
->first();
return $user->childs()->where('id_child', $childId)->first();
// Or
$user = User::with(['childs' => function ($query) use ($childId) {
$query->where('id_child', $childId);
}])
->where('name', 'DoctorWho')
->first();
return $user->childs->first();
// Or
$child = Child::whereHas('user', function ($query) {
$query->where('name', 'DoctorWho');
})
->find(3);
return $child;
Edit :
If you already have the user model then you can do this.
$child = Child::where('id_user', $user->id_user)
->where('id_child', 3)
->first();
return $child;

How to return a collection of models with selected columns and relation eager loaded in Laravel 5.2

Problem details:
I have three models
a Directorate with id and name fields,
an Employee with id and name fields and
a Telephone with id, tel, employee_id, directorate_id, description and type fields. The employee_id may be nullable, that is there are telephones stored in database with employee_id = null
The models are related as follows:
an employee may have many telephones
a directorate, may have many telephones
class Directorate extends Model
{
public function telephones()
{
return $this->hasMany(Telephone::class);
}
public function employees()
{
return $this->hasMany(Employee::class);
}
}
class Employee extends Model
{
public function telephones()
{
return $this->hasMany(Telephone::class);
}
public function directorate()
{
return $this->belongTo(Directorate::class);
}
}
class Telephone extends Model
{
public function employee()
{
return $this->belongsTo(Employee::class);
}
public function directorate()
{
return $this->belongsTo(Directorate::class);
}
}
Question:
I want to fetch a Collection of all the Telephone models that belong to a specific Directorate, that have employee_id = null and also having their directorate relation eager loaded. In addition, from that resulting collection of Telephone models, I need only some of the models' fields, that is id, tel and description
Tries
What I tried so far was the following:
I created a query scope in the Telephone model:
public function scopeHaveNoEmployeeId($query)
{
return $query->where('telephones.employee_id', '=', null);
}
In my controller
$myTelephones = Telephone::with('directorate')
->haveNoEmployeeId()
->where('directorate_id', $directorateId)
->get(['id', 'tel', 'description']);
However what I am receiving are the requested fields of the filtered models without the relation eager loaded, for instance:
[
{
"id": 79,
"tel": "0648136867",
"directorate": null
},
{
"id": 380,
"tel": "0223796011",
"directorate": null
}
]
I tried also to lazy eager load the relation afterwards but with no luck.
Finally I noticed that if I request all the Telephone models fields, the relation will eager load as I request. For example:
$myTelephones = Telephone::with('directorate')
->haveNoEmployeeId()
->where('directorate_id', $directorateId)
->get();
Then as a result:
[
{
"id": 79,
"tel": "0648136867",
"directorate": {
"id": 23
"name": "Some name"
}
},
{
"id": 380,
"tel": "0223796011",
"directorate": {
"id": 23
"name": "Some name"
}
}
]
Your telephone does not have any relationship with the directorate model.
put this in your Telephone model.
public function directorate()
{
return $this->belongsTo(Directorate::class);
}
Actually, after digging into Laravel's details for a while, I noticed that my initial question was out of context and somewhat silly. I was first eager loaded a relationship and then fatuously I filtered out the relationship by not including it in get() parameters. I had just to do the following:
$myTelephones = Telephone::with('directorate')
->haveNoEmployeeId()
->where('directorate_id', $directorateId)
->get(['id', 'tel', 'description', 'directorate']);

Resources