How to validate a relation property in Laravel? - laravel

What I have
I have a Room model with a has many relation to Reservation. Room has a property is_open indicating that the given room is available.
Goal
I would like to be able to validate in the rules function of a FormRequest that a room is available when I store a new reservation based on the is_open property.
What I tried
The validated data is in an array so the rule key needs to be this: data.room_id. I know that exists can validate something in the database but I don't know how its syntax works. Also how can I validate a property of a relation?
class ReservationStoreRequest extends FormRequest {
public function authorize() {
return true;
}
public function rules() {
return [
'data.room_id' => 'exists:' // what goes here?
];
}
}

tl;dr
public function rules() {
return [
'data.room_id' => [
Rule::exists('rooms', 'id')->where(function ($query) {
$query->where('is_open', 1);
})
]
];
}
Validating with exists
There is documentation on the exists rule but it is really concise. Lets see what the commands in the docs actually mean. The point of this rule is that it can validate the existence of a field in a table in your db given by some criteria. For example, when you save a reservation you can validate that the room you are reserving is exists in the database.
Basic usage
'size' => 'exists:rooms'
This will check the rooms table if there is an entry where the size column contains the value that is the value under validation (in this case the corresponding value of key size which is large). This results in the following SQL query:
select count(*) as aggregate from `rooms` where `size` = "large";
Specifying a custom column name
In the previous section the validated column's name comes from the key of the validated property ('size'). You might want to give a different column name because you are validating a column that is named differently from your data's key (e.g. 'size' is stored in the 'category' column) another case is that your rooms table's primary key is just id instead of room_id like in the validated data. In your case the validated data's key is data.room_id but you would like to check the id column of rooms. You can specify the column's name separated by a comma:
'data.room_id' => 'exists:rooms,id'
This will check the rooms table if there is an entry where the id column contains the value under validation resulting in the following SQL query:
select count(*) as aggregate from `rooms` where `id` = 4;
Specify a custom column name with extra clauses
If you need to further filter the results you can use the Illuminate\Validation\Rule class. You pass the table and column name (the second being optional just like in the basic usage section) and in an anonymous function add the extra clauses to the query. You can use every input data in the request, just retrieve it with $this->input('propertyName') or $this->all() then processing it by hand:
'data.room_id' => [
Rule::exists('rooms', 'id')->where(function ($query) {
$query
->where('is_open', 1);
->where('size', $this->input('data.size'));
})
]
The resulting SQL query is this:
select count(*) as aggregate from `rooms` where `id` = 4 and (`is_open` = 1 and `size` = large);
The available clauses in the $query variable are where, whereNot, whereNull, whereNotNull, whereIn and whereNotIn. Check out the Illuminate/Validation/Rules/Exists class docs.

Related

Getting the result of a trailing pivot table

Basically, I have three tables which are users, position, user_position and I want to get all users with their respective positions
Note that a single user can have multiple positions
The `user_position` table consists of:
- user_id
- position_id
To illustrate... it goes something like this:
users-->user_position<--position
What I got so far is this:
User Model
public function user_positions() {
return $this->hasMany('App\Models\UserPosition', 'user_id', 'id');
}
UserPosition Model
public function positions() {
return $this->hasMany('App\Models\Position', 'position_id', 'id');
}
UserController
public function getallusers() {
$data = User::with('user_positions')->get();
return response()->json($data);
}
So far it gives me the correct result set.
For example:
[{id:1,
name:'John',
user_positions:
[{id:1,
user_id:1,
position_id: 5
},
{id:2,
user_id:1,
position_id: 9
}
]
}]
However, it is incomplete, I also wanted the positions array inside the user_positions array but I don't know or I got lost on how to associate/merge/attach (i dont know the right term) the positions function to my User Model.
What you are looking for is a BelongsToMany relation.
You could add the following relation to your User model.
public function positions()
{
return $this->belongsToMany(Position::class);
}
You could eager load them with:
User::with('positions')->get();
Result:
[{
...
"positions": [{ ...The data of the position... }]
}]
When you want to m:m relation, it's advised to use BelongsToMany, there is no need for an intermediate model for the "pivoted" table.
If you have non-eloquent standard naming conventions. Then you have to specify the table name in the 2nd parameter as followed:
return $this->belongsToMany('App\Models\Positions', 'tbl_positions_assigned');
In the 3rd and 4th parameter you specify the foreign key names.
I'd like to refer to you to the documentation Eloquent-relationships as it's very thoroughly explained over there.
One very strong thing you can do with belongsToMany is to append extra columns in the pivot table. Those are also easily obtained from the pivot table. This is useful if you wish to store extra data values specific to the model.
return $this->belongsToMany('App\Models\Positions')->wherePivot('active', 1);
With above example you could exclude certain values, that are not active in this example, when you retrieve the results from the query.

How to know what columns are presents in a Eloquent query before to execute it in Laravel 5.5?

Im using Laravel 5.5 and I have and QueryBuilder object (from the "Illuminate/Database/Eloquent/Builder" class).
I want to set an orderBy sentence into my query, but only if this field is present and exists in the QueryBuilder object (as column in the select section sentence).
For example, there is an User model, with the following fields ['id', 'firtsname', 'lastname', 'username','description'].
This is my object:
Use App\User;
$query = User::query();
if ($request->input('sort') != null) {
$model_query->orderBy($request->input('sort'), 'ASC');
}
$users = $query->get();
When I execute it, works fine (if I send you consistent data, of course). But if I set a column what does not exists, it sends and exception. So, the question is, how I can get the columns to retrieve from my $query object? To validate it, and if it's presents, execute the ordening code.
To answer your question, you can get the presence status of a column using Schema::hasColumn()
if (Schema::hasColumn('users', $request->sort)) {
//
}
GOING FURTHER
Now this doesn't seem very efficient, and maybe potentially leak data. Better validating your sort input and accept only proper column names:
$request->validate(['sort' => 'in:column1,column2']);

Laravel HasManyThrough CrossDatabaseRelation

strange question:
I have 3 Models
Order
with id as PK
Orderline
with id as PK and order_id as FK. brand and partnumber are two separatet colums
Article
with combined PK brand and partnumber
**which is on another database **
One Order hasMany Orderlines. Every Orderline hasOneArticle.
i had make a function within order:
public function articles()
{
$foreignKeys = [
'brand_id',
'article_number',
];
$localKeys = [
'brand',
'partnumber',
];
return $this->hasManyThrough('App\Models\Masterdata\Articles','App\Models\Oms\OrderLine',$foreignKeys,$localKeys,'id','id');
}
How can i retrieve all Attributes from articles through Order?
I tried something like this:
$order = Order::find($orderid)->articles();
dd($order);
//did not work
$order = Order::with('orderlines.articles')->where('id','=',$orderid)->get();
Do you have an idea for me?
You can configure more than one database in the database.php config, and specify the $connection name in the Model class.
For the most part, Eloquent doesn't do JOINs, it just does IN statements on the key values from the preceding query, and programmatically marries the results together after the fact. Partly to avoid the mess of keeping table aliases unique, and partly to offer this kind of support- that your relations don't need to live in the same database.
In other words, what you have there should work just fine. If there's a specific error getting thrown back though, please add it to your post.

Laravel whereHas ManyToMany relation but then order by a value in the third table

I have 3 tables
entry (
id,
title,
other_stuff)
entry_award (
id,
award_id,
entry_id)
award (
id,
name,
type)
I am trying to create a laravel query which lets me get all the entries that have awards, and order them by award.type ASC
$Entries = Entry::with('award')
->whereHas('award', function ($query) {
$query->orderBy('award.award_id','ASC');
})->paginate(20);
But this doesn't work.
This is the sql version of it
SELECT DISTINCT entry.*
FROM entry, award, entry_award
WHERE entry.id = entry_award.entry_id
AND award.id = entry_award.award_id
ORDER BY award.type ASC;
Now I tried to just use the raw sql for it, but the problem seems to be that laravel does not then recognize the result as Entry models/objects. And i need to get other Entry relations later on in the html via blade.
So how can I either make a query-builder query that gets me all entries that have awards and orders them by award.type value
or use the raw sql but have Laravel see it as an array of Entry
objects instead of just an array of JSON values.
class Entry extends Model {
public function entry_award(){
return $this->belongsToMany('App\Award', 'entry_award');
}
}
class Award extends Model {
public function entries() {
return $this->belongsToMany('App\Entry', 'entry_award');
}
}

How to get data using yii2 active records and relations?

I have below code which don't works as exepcted.
Relations specified in model:
public function getShift() {
return $this->hasOne(Shift::className(), ['id' => 'shift_id']);
}
public function getShiftName() {
return $this->shift->name;
}
Query:
$job_position = JobPositions::find()
->innerJoinWith(['shift'])
->where('month(date) = month(curdate())')
->one();
It give me data of on job_position table and not of shift table. How to fetch the data of shift table ?
You should simply use $job_position->shift, it will return the related Shift object.
You should also read this : Accessing Relational Data.
When you declare a relation named xyz via a getter method getXyz(), you will be able to access xyz like an object property. Note that the name is case sensitive.

Resources