Querying ORM with WHERE clause - Eloquent/Laravel 4 - laravel-4

The relevant code is at: https://gist.github.com/morganhein/9254678
I have nested resource controllers, which query similarly structured tables in a database.
If I go to www.example.com/v1/jobs/1/departments, I want to query all departments that are associated with job 1. However I cannot figure out how to do that using the ORM.
Help?

I didn't test, but I would suggest you to try something like this:
Route::resource('/v1/jobs/{id}/departments', 'DepartmentController');
Route::resource('/v1/jobs', 'JobController');
After that, your DepartmentController methods will receive one argument, which is job id in your case. It is easier to use find() method if you are using id to retrieve any specific model. When you found the model you can get access to related models.
class DepartmentsController extends BaseController {
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index($jobId)
{
Clockwork::startEvent('List_Default_Departments', 'Starting.');
$positions = Auth::user()->jobs()->find($jobId)->departments;
Clockwork::endEvent('List_Default_Departments', 'Done.');
return Response::json($positions->toArray());
}
}
Note: There is a different between $job->departments() (returns Builder object to create more complex queries) and $job->departments (returns Collection object directly).
Also if you would like to get the list of jobs with all related departments you can always call:
$jobs = Auth::user()->jobs()->with('departments')->get();

Related

Laravel Model Define Parameters Won't Insert

I am currently moving over from symfony to laravel, it's quite a bit different when it comes to the database. So i have a basic model, i'm just going to use an example:
class Test extends Model
{
use HasFactory;
}
All good, i have a migration and the table created. However, i don't like this:
$test = new Test();
$test->my_field = 'hello';
$test->save();
I don't like it because it's having to use a magic __set() to create the parameter, if i define the parameter in my model like this:
class Test extends Model
{
use HasFactory;
public ?string $my_field;
}
I get database errors when it tries to insert when i define the params like this. Why is that? It's doing the same thing as __set() but i'm actually physically defining them, which in my opinion is a better way to code it as my IDE can typehint and it's just nicer to follow the program knowing what params are there.
What's the reason for it inserting when i don't define them, and not when i do? From my actual table which is bookings , has a field booking_ref:
General error: 1364 Field 'booking_ref' doesn't have a default value (SQL: insert into booking_reviews (updated_at, created_at) values (2021-12-13 14:13:08, 2021-12-13 14:13:08))
This happens when i define the $booking_ref param on the model, but if i take it out and rely on the __set() method it works fine. Doesn't make any sense to me right now.
I think this is a reasonable enough misunderstanding to be useful to future visitors, so I want to try to explain what's going on with some pseudo-code and some references to the current source code.
You are correct that when setting a property on a Laravel model, that is a column in the DB, internally Laravel is using the PHP magic method __set.
What this does is allow you to 1) set properties directly instead of calling some kind of setter function, and 2) interact with your table columns without needing the boilerplate of column definitions in your model.
Where the assumptions go wrong is with what __set is doing. __set does not have to simply set an actual property with the same name. __set is just a method you may implement to do whatever you want. What you assumption implies is that it's doing something like this:
public function __set($key, $value)
{
$this->{$key} = $value;
}
However, you can do whatever you want with the $key and $value passed to the magic method.
What Laravel does is call another method defined in the HasAttributes trait - setAttribute.
public function __set($key, $value)
{
$this->setAttribute($key, $value);
}
setAttribute does a few extra things, but most importantly it adds the key/value pair to Model property $this->attributes[].
To hopefully help this difference make sense, here is what the two __set methods would yield with a basic example:
$model->my_column = 'value';
// 1st example
/**
* {
* public $my_column = 'value';
* }
*/
// Laravel way
/**
* {
* protected $attributes= ['my_column => 'value'];
* }
*/
I won't go through both saving and updating since they're very similar, but to show how this is used, we can look at the save method, which calls performInsert and after a few more calls makes it's way back to the attributes property to determine what to actually insert into the query.
Summary
Laravel does not use custom model properties when deciding what column/values to add to queries.
This is why when you create custom mutators, you interact with the attributes property just like Laravel does internally.
Anytime you introduce "magic" into code, you have some tradeoffs. In this case, that tradeoff is slightly less clarity with what database columns are actually available. However, like I mentioned in comments, there are other solutions to make models more IDE friendly like Laravel IDE helper.

Laravel Eloquent getting data (with relationship) confusion

I am a bit confused on how to properly pull the data from Eloquent Models. I did read the documentation, but this is not mentioned anywhere or I missed it.
Let's assume we have two models, Client and Country (note: I only added relevant code, otherwise this question would be quite long):
Client:
<?php
namespace App\Models\Client;
use App\Models\BaseModel;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* #property int id
* #property int country_id
*/
class Client extends BaseModel
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
...
'country_id',
...
];
/**
* Return Country relationship
*
* #return BelongsTo
*/
public function country(): BelongsTo
{
return $this->belongsTo(Country::class);
}
}
Country:
<?php
namespace App\Models\Country;
use App\Models\BaseModel;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* #property int id
*/
class Country extends BaseModel
{
public function clients(): hasMany
{
return $this->hasMany(Client::class);
}
}
Nothing special, two normal Models. Now, when I try to pull the data I have a few issues:
dd(Country::findOrFail(32)); // CORRECT: returns selected country
dd(Country::findOrFail(32)->first()); // WRONG: returns first country in database, not the one with ID of 32, EXPECTED: single country object with selected country
dd(Country::findOrFail(32)->get()); // WRONG: returns all countries in database, EXPECTED: an array of objects with one country in it
dd(Country::with('clients')->findOrFail(32)->first()); // WRONG: returns first country in the database and not the one with ID of 32, relationship is also not correct, EXPECTED: object with selected country with valid relationship (clients) as array of objects
dd(Country::findOrFail(32)->clients()); // WRONG: returns HasMany relationship, no data, EXPECTED: an array of clients as objects
dd(Country::findOrFail(32)->clients()->get()); // CORRECT: returns valid data, array of clients as objects
dd(Country::findOrFail(32)->clients()->first()); // CORRECT: returns first client for selected country
So, why are all WRONG wrong ones? Am I interpreting the documentation wrongly? The most frustrating is dd(Country::with('clients')->findOrFail(32)->first()) since now I have no means on how to filter based on selected country and then also provide a list of clients in that country. I thought that Eloquent is quite advanced, so I assume that I am doing something wrong and would appreciate some guidance.
Please note that in latest example I also tried reversing query as dd(Country::findOrFail(32)->with('clients')->first()), same result.
In my opinion, you are mixing up a few things here. For example, combining FindOrFail() with first() is double. Look here, that's a good explanation I found:
find($id) takes an id and returns a single model. If no matching
model exist, it returns null.
findOrFail($id) takes an id and returns a single model. If no matching model exist, it throws an error1.
first() returns the first record found in the database. If no matching model exist, it returns null.
firstOrFail() returns the first record found in the database. If no matching model exist, it throws an error1.
get() returns a collection of models matching the query.
pluck($column) returns a collection of just the values in the given column. In previous versions of Laravel this method was called lists.
toArray() converts the model/collection into a simple PHP array.
Source: https://stackoverflow.com/a/33027466/14807111
The method description for the Builder method FindOrFail() is:
* Find a model by its primary key or throw an exception.
In your usecase this method is calling the find()method and executing:
$this->whereKey($id)->first($columns);
This will return the first model and by default all of its columns. Therefore any other first() afterwards will introduce another query.
Your first three "Wrongs" are because of execution order, the query executes the findOrFail first and afterwards it calls first() on the whole query again.
The fourth "WRONG" happens because your relationship returns a hasMany relationship, this is normal behaviour. You could either call ->get() afterwards or call ->clients without the () to receive the collection.
After all you are using the first() method wrong. You either use find(), findOrFail() or you use ->first(). But you should not use them together (there might be some edge cases where you have to, but genreally speaking, don't!).
Using find() or findOrFail() will already return the first() element that matched the ID.

Join multiples fields from different tables with Laravel Model

i try return multiples fields from different tables in just one request, example
properties table
-id
-name
-address
rules table
-id
-checkin
-propertyId
i hope a response just like that
[
id=>123,
name=>Name,
address=>Main Street,
checkin:10:00
]
And i would like that this can be used by default in all futures request, all(), first(), get()
Note: I can't use relationship because I need to join two tables in just one
Simple solution set the default with on the property class to include the rule.
protected $with = ['rule'];
To fill you exact spec, add a checkin getter on the property model and use the append property to add it to the serialization.
protected $appends = ['checkin'];
public function getCheckinAttribute()
{
return $this->rule->checkin;
}
Extending the query builder
Laravel Eloquent Query Builder Default Where Condition
Applying logic for all your model queries
Or for the default scope
You could do it this was
https://laraveldaily.com/model-default-ordering-assigning-global-scope/

API Platform - Which approach should I use for creating custom operation without entity

I'm new to API Platform. I think it's great but I cannot find any example how to create custom endpoint that isn't based on any entity. There are a lot of examples based on an entity and usually they are all about CRUD. But what about custom operations?
I need to create custom search through database with some custom parameters which aren't related to any entity.
E.g. I want to receive POST request something like this:
{
"from": "Paris",
"to": "Berlin"
}
This data isn't saved to db and I haven't entity for it.
After I receive this data, there should be a lot of business logic including db queries through a lot of db tables and also getting data from external sources.
Then, after the business logic is finished, I want to return back result which is also custom and isn't related to any entity.
E.g.
{
"flights": [/* a lot of json data*/],
"airports": [/* a lot of json data*/],
"cities": [/* a lot of json data*/],
.......
}
So, I think I'm not the only on who does something similar. But I really cannot find a solution or best practices how to do this.
In the documentation I've found at least three approaches and I cannot implement none of them.
The best one, I guess the most suitable for me it is using Custom Operations and Controllers. But documentation says this one is not recommended. Also I think I should use DTOs for request and response, but for this approach I'm not sure I can use them.
The second one I found it's using Data Transfer Objects, but this approach requires an entity. According to the documentation, I should use DTOs and DataTransformers to convert DTO to an Entity. But I don't need entity, I don't need save it to db. I want just handle received DTO on my own.
The third one I guess it is using Data Providers, but I'm not sure it is suitable for my requirements.
So, the main question is which approach or best practice should I use to implement custom operation which isn't related to any entity. And it will be great use DTOs for request and response.
You are not forced to use entities. Classes that are marked with #ApiResource annotation may not be entities. Actually, if your application is smarter than basic CRUD you should avoid marking entities as ApiResource.
Since you want to use POST HTTP method (which is for creating resource items) you can do something like this.
1) Define class describing search fields and which will be your #ApiResource
<?php
// src/ApiResource/Search.php
namespace App\ApiResource;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Action\NotFoundAction;
use ApiPlatform\Core\Annotation\ApiProperty;
use App\Dto\SearchResult;
/**
* #ApiResource(
* itemOperations={
* "get"={
* "controller"=NotFoundAction::class,
* "read"=true,
* "output"=false,
* },
* },
* output=SearchResult::class
* )
*/
class Search
{
/**
* #var string
* #ApiProperty(identifier=true)
*/
public $from;
/** #var string */
public $to;
}
2) Define DTO that will represent the output
<?php
// src/Dto/SearchResult.php
namespace App\Dto;
class SearchResult
{
public $flights;
public $airports;
public $cities;
}
3) Create class that will inplement DataPersisterInterface for handling business logic.
It will be called by framework because you make POST request.
<?php
// src/DataPersister/SearchService.php
declare(strict_types=1);
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use App\Dto\SearchResult;
use App\ApiResource\Search;
final class SearchService implements DataPersisterInterface
{
public function supports($data): bool
{
return $data instanceof Search;
}
public function persist($data)
{
// here you have access to your request via $data
$output = new SearchResult();
$output->flights = ['a lot of json data'];
$output->airports = ['a lot of json data'];
$output->cities = ['inputData' => $data];
return $output;
}
public function remove($data)
{
// this method just need to be presented
}
}
That way you will recieve results based on request.

Laravel - eager loading filtering related model on different database instance

i'm trying to implement filtering as defined here.
This works (to filter model A from controller A), however controller A/model A has a relation to model B, which is what I want to filter, as well as a 3rd relationship to model C from model B.
Model A is hosted on DB instance 1, Model B is hosted on DB instance 2 and are totally separated.
These relationships, without any filters, work fine.
Trying to mess around, I tried something like the below, which clearly does not work, however will hopefully illustrate what i'm trying to do. This filter gets applied to model A
protected function sn($sn)
{
$s= Bid::where('crm', function ($query) {
$query->where('sNumber', '=', 'SN512345');
})->get();
return $s;
}
SQLSTATE[42S22]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Invalid column name 'crm'. (SQL: select * from [bids] where [crm] = (select * where [sNumber] = SN512345))
Bid is model A/controller A, CRM is model B which is the one I want to filter.
I thought about having numerous different functions in the model to filter, however I don't know if this was the best solution and I thought it was better to get it all out into another class.
I tried the below, which does not work as it applies the query to DB1.
$s= Bid::with('crm')->whereHas('crm', function ($query) {
$query->where('sNumber', '=', 'SN512345');
})->get();
[SQL Server]Invalid object name 'Opportunity'. (SQL: select * from [bids] where exists (select * from [Opportunity] where [bids].[crm_ref] = [Opportunity].[sNumber] and [sNumber] = SN512345))
Is there a way to implement this in some coherent and reusable way? I was thinking something along the lines of load Bid::, load CRM:: with applied filters, then append CRM:: to Bid:: somehow in the normal way that Eloquent would do this.
Thanks.
EDIT:
I am using the following filter in BidFilter.php
protected function sn($sn)
{
$users = DB::connection('sqlsrv_crm')->table('OpportunityBase')->select('*')->where('new_SalesNumber', '=', $sn)->get();
return $users;
}
And this filters the result set, as I can see in the debug bar:
debug bar queries
However this is also loading the normal unfiltered eager loaded CRM relationship. How can I switch to the filtered CRM results instead of the default unfiltered?
BidController index method:
public function index(BidFilter $filters)
{
$bids = $this->getBids($filters);
return view('public.bids.index', compact('bids'));
}
BidFilter
public function index(BidFilter $filters)
{
$bids = $this->getBids($filters);
return view('public.bids.index', compact('bids'));
}

Resources