Passing computed parameters to middleware - laravel

I would like to pass a computed value into my middleware through my web.php routes file. I have this model configuration:
Planet hasMany Countries
Country hasMany Cities
City hasMany Buildings
In all of my routes I need to build a menu that helps navigate between planets. So, no matter what I'm doing I need to be able to access the planet->id. Right now, in my middleware I have a long series of if/elseif checking to see which parameters exist in the URL:
if (isset($parameters['planet']))
$planetId = $parameters['planet']->id;
else if (isset($parameters['country']))
$planetId = $parameters['country']->planet->id;
else if (isset($parameters['building']))
$planetId = $parameters['building']->country->planet->id;
Obviously this gets very unwieldy very quickly and I feel like there should be a better way to pass the planetId into the middleware but I cannot find any way to gracefully do that...
Thank you very much for your help!

You're better off just adding "planet_id" to all of your models that use their planet reference frequently.
Writing logic with a bunch of nested relationship referencing can really take a toll on the speed of your application. Writing the extra data which seems counter intuitive at first, will actually really help. You want to prioritize the speed of your SELECT queries over the size of your database where you can reasonably. Your SELECT queries will be the majority of your applications database usage. In our modern day having a few extra bytes isn't that big of a deal and it would serve a huge purpose here.
Add planet_id to your countries and buildings migration table.
Add relationship method "planet" to your country and building model class.
Then you can easily do something like:
function getPlanetFromRequest($request)
{
$parameters = $request->all();
$planetParameterNames = ['building', 'country', 'planet'];
foreach ($planetParameterNames as $planetParameterName) {
if (isset($parameters[$planetParameterName])) {
$model = $parameters[$planetParameterName];
// if the model is a planet, just return its self.
if ($model instanceof Planet) {
return $model;
}
// $model would be country or building.
return $model->planet;
}
}
return null;
}
If you only need the planets id, save yourself a query and just reference the models planet_id rather than referencing its planet and then grabbing the id.
$country->planet_id; // NOT $country->planet->id;

Related

How to select multiple row values coma separated in laravel

I am trying to get all the ids with coma separated while doing eloquent relationship.
So here is my current queries
Divrank::where('division_id', 591)->with('meta')->orderBy('position', 'asc')->get()
Divrank table has a one to many relation with Divrankmeta model. So with meta I am trying to return
public function meta(){
return $this->hasOne(Divrankmeta::class)->selectRaw('id, match_id,divrank_id, sum(won) as won, sum(loss) as loss, sum(draw) as draw, sum(points) as points, sum(matchePlayed) as matchePlayed, sum(totalSets) as totalSets, sum(totalGames) totalGames')
->groupBy('divrank_id');
}
So far this query works fine..
I get the result like this screenshot
Ok so in my Divrankmeta model, I have a column called winAgainst and it can have some ids and some left null. So with the meta relation I want to retrieve winAgainst ids with coma separated string inside meta object.
For better understanding, here is how Divrankmeta table looks like
How can I do this?
Thank you.
The relation you created is one-to-one not one-to-many. That's why you are getting a meta object of the first matched row instead of an array that contains all related meta records.
I never put the modification codes into the eloquent functions. Those codes seem belongs to somewhere else. From my perspective, using "resources" and modifying the data there is a better idea.
If you chose the do so:
// Divrank.php
public function metas()
{
return $this->hasMany('App\Models\Divrankmeta');
}
// Divrankmeta.php
public function divrank()
{
return $this->belongsTo('App\Models\Divrank');
}
// DivrankController
public function index()
{
return DivrankResource::collection(Divrank::with("metas")->all());
}
Create a resource file.
php artisan make:resource DivrankResource
Now, you can modify your Divrank collection on the resource file before your controller returns it.
public function toArray($request)
{
$metaIds = [];
forEach($this->metas as $meta) {
array_push($metaIds, $meta['id']);
}
$this['metaIds'] = $metaIds;
return parent::toArray($request);
}
I'm not able to test this code. But it will probably work. If you don't want to use resources, you can create the same functionality in your controller as well. Bu we like to make controllers as short as possible.
Ok I think I solved it, These are the changes I did. Thanks
return $this->hasOne(Divrankmeta::class)
// ->selectRaw('id, match_id,divrank_id, sum(won) as won, sum(loss) as loss, sum(draw) as draw, sum(points) as points, sum(matchePlayed) as matchePlayed,
// sum(totalSets) as totalSets, sum(totalGames) totalGames')
->select(\DB::raw("id, match_id,divrank_id, sum(won) as won, sum(loss) as loss, sum(draw) as draw, sum(points) as points, sum(matchePlayed) as matchePlayed,
sum(totalSets) as totalSets, sum(totalGames) totalGames, GROUP_CONCAT(winAgainst) as winAgainst"))->groupBy('divrank_id');

Best Practice - Laravel Controller Eloquent merge

I have a scope on my Supplier model that returns results where active = true.
This works great when creating new entries, as I only want the user to see active suppliers.
Current entries may have an inactive supplier; When I edit it, I want to see all active Suppliers, plus the current supplier (if it is inactive)
I have this code in my controller:
$suppliers = Supplier::active()->get();
if (!$suppliers->contains('id', $record->supplier->id))
{
$suppliers->add(Supplier::find($record->supplier->id));
}
Two questions: Is this the correct way to do this? Should this code be in my controller or should I have it somewhere else? (perhaps a scope but I wouldn't know how to code that).
Edit:
Thanks for the help guys. I have applied advice from each of the answers and refactored my code into a new scope:
public function scopeActiveIncluding($query, Model $model = null)
{
$query->where('active', 1);
if ($model && !$model->supplier->active)
{
$query->orWhere('id', $model->supplier->id);
}
}
What you've written will work, but the Collection::contains function can potentially be pretty slow if the collection is large.
Since you have the id, I would probably make the following change:
$suppliers = Supplier::active()->get();
$supplier = Supplier::find($record->supplier->id);
if (!$supplier->active) {
$suppliers->add($supplier);
}
Of course, the downside to this is that you may be making an unnecessary query on the database.
So you have to consider:
is the record's supplier more likely to be active or inactive?
is the size of the collection of active suppliers large enough to justify another (potentially wasted) call to the database?
Make the choice that makes the most sense, based on what you know of your application's data.
As for the second question, if you will only need this specific set of suppliers in this one part of your application, then the controller is a good place for this code.
If, however, you will need this particular set of suppliers in other parts of your application, you should probably move this code elsewhere. In that case, it might make sense to create a function on the the related model (whatever type $record is...) that returns that model's suppliers set. Something like:
public function getSuppliers()
{
$suppliers = Supplier::active()->get();
$supplier = $this->supplier;
if (!$supplier->active) {
$suppliers->add($supplier);
}
return $suppliers;
}
I saw #Vince's answer about 1st question, and I'm agree with him.
About 2nd question:
Write scope in Supplier model like this:
public function scopeActive($query){
$query->where('active', 1); // for boolean type
}
For good practice, you need to write the logic parts in services like "App\Services\SupplierService.php". And there write the function you want:
public function activeSuppliersWithCurrent($record) {
$suppliers = Supplier::active()->get();
$supplier = Supplier::find($record->supplier->id);
if (!$supplier->active) {
$suppliers->add($supplier);
}
}
In your SupplierController's constructor inject the instance of that service and use the function, for example:
use App\Servives\SupplierService;
protected $supplierService = null;
public function __construct(SupplierService $supplierService) {
$this->supplierService = $supplierService;
}
public function getActiveSuppliersWithCurrent(...) {
$result = $this->supplierService->activeSuppliersWithCurrent($record);
}
As you can see, later you will not need to change anything in controller. If you'll need to change for example the query of suppliers selection, you will just have to change something only in service. This way will make your code blocks separated and shorter.
Also the sense for this pattern: you don't need to access the models from controller. All logic related with models will implemented in services.
For other projects you can grab only services or only controllers, and implement another part differently. But in that case if you had all codes in controller, that will prevent you to grab the portions of necessary codes, cuz may you don't remember what doing each blocks...
You could add a where clause to the query to also find that id.
$suppliers = Supplier::active()->orWhere('id', $record->supplier->id)->get();
You could potentially slide this into the active scope by passing the 'id' as an argument.
public function scopeActive($query, $id = null)
{
$query->where('active', true);
if ($id) {
$query->orWhere('id', $id);
}
}
Supplier::active($record->supplier->id)->get();
Or make another scope that does this.

Laravel polymorphic hasMany relationship

From Laravel's docs, the model polymorphism is defined as follows:
Polymorphic relations allow a model to belong to more than one other model on a single association
Sounds like it's designed to work with belongsTo instead of hasMany side. Here's a scenario that I want to achieve:
In my system, there are many project types, each projec type will have its own invoice field layout. Let's say we have a Project model that has a type field, whose value could be contract or part-time. We have another two tables called ContractInvoice and PartTimeInvoice to define their respective field layout, both of these invoice tables have a project_id referencing a project record. What I want to do is I want a universal interface to retrieve all invoices given a project, something like $project->invoices.
Current solution
I can't figure out how to achieve this via polymorphism. So what I am currently doing is kind silly, using a switch statement in my invoice() method on Project model class:
switch ($this->type) {
case 'contract':
$model = 'App\ContractInvoice';
break;
case 'part-time':
$model = 'App\PartTimeInvoice';
break;
}
return $this->hasMany($model);
I feel like there must be a better way to do this. Can someone please shed some light?
I don't see how a polymorphic relationship would be beneficial in this case. If you had different project type models and a single invoices table, then the invoices could morphTo the projects. But as you've described it, the switch statement sounds like it is adequate. You could achieve the same means using when conditionals like:
public function invoices()
{
return $this->when($this->type === 'contract', function () {
return $this->hasMany(ContractInvoice::class);
})->when($this->type === 'part-time', function () {
return $this->hasMany(PartTimeInvoice::class);
});
}
The type attribute on the Project model and the separate invoice tables are defining a rigid relationship between them, which goes against the idea of polymorphism. Think likes for comments and posts.

Laravel - Retrieve the inverse of a many-to-many polymorphic relation (with pagination)

after some digging I still could not find any solid way to retrieve the inverse of a many-to-many polymorphic relation that allows mixed models results.
Please consider the following:
I have several models that can be "tagged". While it is trivial to retrieve for example $item->tags, $article->tags and the inverse with $tag->articles and $tag->items I have no easy way to do something like $tag->taggables to return both articles and items in the same collection. Things get even bumpier as I need to use pagination/simple pagination to the query.
I have tried a few workarounds but the best I could put together still looks crappy and limited. Basically:
I queried the DB once per "taggable";
put all in a single big collection;
passed the collection to a phpleague/fractal transformer (my API uses it) that returns different json values depending on the parsed models.
The limits of this approach is that building a pagination is a nightmare and fractal "include" options can't be used out of the box.
Can anyone help me? I'm currently using Laravel 5.1.
There is not much magic in my current code. Faking and simplifying it to make it short:
From the api controller:
$tag = Tag::findOrDie($tid);
$articles = $tag->cms_articles()->get();
$categories = $tag->cms_categories()->get();
$items = $tag->items()->simplePaginate($itemsperpage);
$taggables = Collection::make($articles)->merge($categories);
// Push items one by one as pagination would dirt the collection struct.
foreach ($items as $item) {
$taggables->push($item);
}
return $this->respondWithCollection($taggables, new TaggableTransformer);
Note: using simplePaginate() is there only because I would like all articles and categories to be shown on first page load while the number of items are so many that need pagination.
From the Transformer class:
public function transform($taggable)
{
switch (get_class($taggable)) {
case 'App\Item':
$transformer = new ItemTransformer;
break;
case 'App\CmsArticle':
$transformer = new CmsArticleTransformer;
break;
case 'App\CmsCategory':
$transformer = new CmsCategoryTransformer;
break;
}
return $transformer->transform($taggable);
}
Please consider that the other transformers are simply returning arrays of data about the models they correlate with. If you use Fractal you would easily spot that nested "included" models would not be applied.
Nothing fancy for the Tag model:
class Tag extends Model
{
protected $morphClass = 'Tag';
protected $fillable = array('name', 'language_id');
public function cms_articles() {
return $this->morphedByMany('App\CmsArticle', 'taggable');
}
public function cms_categories() {
return $this->morphedByMany('App\CmsCategory', 'taggable');
}
public function items() {
return $this->morphedByMany('App\Item', 'taggable');
}
// Would love something like this to return inverse relation!! :'(
public function taggables() {
return $this->morphTo();
}
}
I am also considering the option to do 3 separate calls to the API to retrieve articles, categories and items in three steps. While in this particular scenario this might make sense after all, I would still need to deal with this particular inverse relation headache with another part of my project: notifications. In this particular case, notifications would have to relate to many different actions/models and I would have to retrieve them all in batches (paginated) and sorted by model creation date...
Hope this all makes sense. I wonder if a completely different approach to the whole inverse "polymorphic" matter would help.
Kind regards,
Federico
Ah yes. I was down your path not all that long ago. I had the same nightmare of dealing with resolving the inverse of the relationship of polymorphic relationships.
Unfortunately polymorphic relationships haven't been given much attention in the Laravel ecosystem. From afar they look like unicorns and rainbows but soon you're fighting things like this.
Can you post an example of a $thing->taggable for a better picture? Think it may be solvable with a dynamic trait + accessor magic.

Relationships and where clauses?

I have the following tables:
**galleries**
id
location
open_to_public
**pictures**
id
title
published
**gallery_picture**
gallery_id
picture_id
Here's my model for galleries:
class Galleries extends Eloquent {
protected $table = 'galleries';
public function pictures(){
return $this->belongsToMany('pictures', 'gallery_picture', 'gallery_id', 'picture_id');
}
I'm trying to select a gallery (id, location) and get it's related pictures (id, title).
To start with I've tried this but it seems to return a huge amount of data, im not sure if I'm doing it correct?
$this->mGalleries = new Galleries;
return $this->mGalleries->pictures();
What I want to also do is add some constraints to the query, I understand I can do it like:
public function scopePublic()
{
return $query->where('open_to_public','=',1);
}
Then:
return $this->mGalleries->pictures()->public();
But I have yet to implement this correctly. Could someone point me in the right direction.
I want to get gallery.id, gallery.location and all of that galleries pictures where gallery.open_to_public = 1 and to only get pictures that published = 1.
Also I would like to get all of the galleries that are relevant to the above conditions and not just a single one.
Without the specific error you're receiving it's hard to say exactly what's going on her,e but one thing that jumped out at me from your code samples is that you don't accept the $query as a parameter to your scope method. Try this code instead:
public function scopePublic($query)
{
return $query->where('open_to_public','=',1);
}
Additionally to that it looks like you're calling your scoped function on the wrong object. From your description you need something like the following set up:
Gallery (model)
scopePublic($query)
Picture (model)
scopePublished($query)
Also, you can only get the pictures of a single gallery, not all galleries in one go. So you can't use Gallery::all()->pictures as you might expect but instead you have to do your own collection building.
Your final code will end up being something like the following:
// empty collection to store all our pictures
$pictures = new \Illuminate\Database\Eloquent\Collection;
// get all galleries open to the public
$galleries = Gallery::public()->get();
// for each gallery, get its pictures and add it to the collection
$galleries->each(function ($gallery) use ($pictures) {
$pictures->merge($gallery->pictures()->published()->get());
});
However, there are a few different ways you could do this. You might find that preloading your relations helps with the database queries too (look up Eloquent's with(), and see if there's a way to pass your scope call in there somehow). Alternatively, if Eloquent's syntax is a little too verbose, you could try using the DB class and joins manually.
Also, sorry if that code is buggy, it's untested but should give you at least a grounding of how to go about solving this.

Resources