I have different classes that act as "dictionary" lists, for example, for CarType class I hava table with id, value fields (1, 'small'; 2, 'big' ..)
Is there a way I can use such model as a map list (with cached data in memory) to have sometthing like that in my blade files:
{{ CartType->value($model->type_id) }}.
The idea would be
to load in memory all the data (or at leas after 2 requests of
different values)
to be easily to wrap around some fields of the model
You can do this with Eloquent (using relationships, as #TimLewis suggests), but it if's a simple map with just a few values (ie. not hundreds of different options), you'll probably find that Eloquent adds a lot of overhead for such a simple function.
Instead, I'd use database queries inside custom accessors. For example,
Imagine you have a Car Eloquent model and each car has exactly one "car type" value (denoted by a car_type field in the database).
Imagine that you also have a table in your database for listing car types, called (not surprisingly) car_types.
Here's a quick diagram:
|------------------|
|cars | |------------------|
|------------------| |car_types |
|id INT PRIMARY_KEY| |------------------|
|car_type_id INT | -- one-to-many --> |id INT PRIMARY_KEY|
|... | |name CHAR |
|------------------| |------------------|
You could set up an Eloquent model for car_type and define a relationship. That would work just fine, but do you really need all the functionality of an Eloquent model for this?
Instead, you could set up an accessor on the cars model:
class Car extends Eloquent {
public function getCarTypeAttribute()
{
return DB::table('car_types')
->select('name')
->where('id', '=', $this->car_type_id);
}
}
This will perform better than a full Eloquent model plus relationships.
But you also asked about caching the list. You could easily expand the example above to store the full list in the cache instead of querying each time:
class Car extends Eloquent {
public function getCarTypeAttribute()
{
$list = Cache::get('car_types_list');
if (!$list) {
$list = DB::table('car_types')->select('name');
// save in the cache for 60 minutes
Cache::put('car_types_list', $list, 60);
}
// search the list for the type ID
foreach ($list as $type) {
if ($type->id===$this->id) {
return $type;
}
}
// the type wasn't found
throw new Exception("That's not right!");
}
}
Now, if you have a Car model, you can get the type like this:
$car = Car::find(1234);
$type = $car->CarType;
Related
I got 3 tables. Table 1 & 2 has their ids as foreign keys in third one(pivot).
Relations for first one is
$this->hasMany("App\Pivot","game_id");
, second is
$this->belongsToMany("App\Pivot","army_id");
and pivot has relationships with both of them i.e belongsTo.
My schema:
I tried accessing it in controller of first one like this:
$games= Game::with("armies")->get();
Result that i get is array of games where instead of individual army data , i get collection from pivot table.
I can loop through it and get it that way, is there more elegant way of doing it?
If you are using pivot table this is the way how to do it.
Games Model
public function armies()
{
return $this->belongsToMany(App\Armies::class, 'pivot_table', 'game_id', 'army_id');
}
Armies Model
public function armies()
{
return $this->belongsToMany(App\Games::class, 'pivot_table', 'army_id', 'game_id');
}
Access the relationship like this..
Controller
App\Games::first()->armies()->get();
or
App\Games::first()->armies
or
App\Games::find(1)->armies
If you're going to use an intermediate table like that I'd probably do something like this:
Games model
public function armies()
{
return $this->belongsToMany('App\Armies');
}
Armies model
public function games()
{
return $this->belongsToMany('App\Games');
}
I'd keep the table structures all the same but rename the "pivot" table to armies_games since this is what Laravel will look for by default. If you want to keep it named Pivots, you'll need to pass it in as the second argument in belongsToMany.
With this, you don't really need the Pivot model, you should just be able to do:
$armies = Game::first()->armies()->get();
or
$armies = Game::find(3)->armies()->orderBy('name')->get();
or
$game = Game::first();
foreach ($game->armies as $army) {
//
}
etc.
So I have this crazy idea that has to do with Laravel and model inheritance. I would like to configure a set of models with a single parent but when I request the child model I would like the data returned. For example I would have a Contacts model which is the parent:
Contacts: id, first_name, last_name, image
Then I would have a series of contact types that inherit from Contacts. Each of these child models would have their own set of fields (i.e. for members I need to know when they joined,etc. but for volunteers I might need to know if they have an up-to-date first-aid certificate). Here are a few examples:
Members: contact_id, joined_on, birthday, medical_concerns
Volunteers: contact_id, current_first_aid, interests
Staff: contact_id, pay_rate
I would love to be able to do something like:
$members = \App\Member::all();
and have the contact AND member data returned as if everything was one row, like this:
+---+------------+-----------+-------+------------+------------+------------------+
|id | first_name | last_name | image | joined_on | birthday | medical_concerns |
+---+------------+-----------+-------+------------+------------+------------------+
| 1 | Fred | Bloggs | null | 2015-01-01 | 1993-10-22 | false |
| 2 | Jim | Phillips | null | 2016-04-30 | 1987-09-22 | true |
+---+------------+-----------+-------+------------+------------+------------------+
And to make it a little more difficult I would like all of the relationships that apply to the parent to work for the child. So I could do something like this:
$members = \App\Member::find(1)->phone
And, even though the Member model doesn't have a relationship defined to the Phone model it would return the phone related to the Contact because the parent has that relationship.
I would also like to be able to specify columns that don't belong to the child when retrieving data and not have Laravel throw an error:
$members = \App\Member::all(['first_name','last_name','joined_on'])
I have messed around with overriding the Eloquent model and writing my own version of the all and find methods which is working but it looks like I may have to override all of the methods to get this to work and maybe that would be more work than just forgoing Eloquent and looking for other (or my own bespoke) solution.
So I guess my questions is: Is there an "easy" way to do this with Laravel or am I trying to make it do things that it was never intended to do?
I think you can do like this:
$members = \App\Members::with('contact')->all();
Of course your Members model should have defined the belongsTo relation to the contact model.
Is there an "easy" way to do this with Laravel or am I trying to make it do things that it was never intended to do?
Yes.
Eloquent doen't manage inheritance in this way. It might be better to implement polymorphic relations instead.
However, overriding just this bit seems to serve your purposes:
abstract class ContactSubclass extends Contact
{
protected $with = 'parent';
public function parent()
{
return $this->belongsTo(Contact::class, 'contact_id');
}
public function newQuery()
{
$contactTable = (new Contact())->getTable();
$thisTable = (new static())->getTable();
$builder = parent::newQuery();
$builder->join($contactTable, "$thisTable.contact_id", '=', "$contactTable.id");
return $builder;
}
public function newFromBuilder($attributes = [], $connection = null)
{
$model = parent::newFromBuilder($attributes, $connection);
if ($model->parent) {
$model->setRawAttributes(array_merge($model->parent->getAttributes(), $model->getAttributes()), true);
}
return $model;
}
protected function getRelationshipFromMethod($method)
{
if (method_exists(parent::class, $method)) {
return $this->parent->getRelationshipFromMethod($method);
}
return parent::getRelationshipFromMethod($method);
}
}
Have Member and other subclasses extend this class (or add these overrides in each class that extends Contact).
(I haven't tested it thoroughly, give it a try. Also, this won't handle eager loads directly; try to find what to override if you want to support that.)
I have been trying to get my head around these polymorphic relationships all day. I might be over complicating/thinking it but. Can Laravel handle inverse polymorphic relationships? I have a registration flow that can have two types of field Models- normal field and customField.
When I loop through all the fields available it could pull the attributes from either NormalField or CustomField.
<?php
foreach($registrationFlow->fields->get() as $field)
{
echo $field->name; // could be custom field or could be normal field
}
?>
My difficulty is that, the example given in the docs works if you want to assign a photo to either staff or orders, but i want to assign either a customField or a normalField to a registrationFlow
*Edit
If you follow the example for the polymorphic many to many relationship, The tag class contains posts and videos- while i would want just a simple fields() method that relates to customField or normalField dependent on the type
First of all, you should take a look at the updated docs for Laravel 5.1: https://laravel.com/docs/5.1/eloquent-relationships#polymorphic-relations.
I think the difficulty with the example they provide is that the relationship between Photo and Staff/Product are "has-a" relationships, whereas you are trying to model an "is-a" relationship. However, you can model "is-a" essentially the same way. Take a look at this article: http://richardbagshaw.co.uk/laravel-user-types-and-polymorphic-relationships/.
Basically, the strategy is to define a generic model (and a generic table), perhaps in your case Field, that relates to your RegistrationFlow. You then have two subtype models, NormalField and CustomField, that have one-to-one relationships with Field. (there's your "is-a"). Thus, RegistrationFlow is indirectly related to your field subtypes.
Polymorphism comes in when you want to access the specific subtypes:
class Field extends Model {
public function fieldable()
{
return $this->morphTo();
}
}
Your base field table should have fieldable_id and fieldable_type columns defined (see the Eloquent docs).
You can then add methods to NormalField and CustomField that let you access the base model (your "inverse relationship"):
class NormalField {
public function field()
{
return $this->morphOne('Field', 'fieldable');
}
}
class CustomField {
public function field()
{
return $this->morphOne('Field', 'fieldable');
}
}
Usage:
$field = Field::find(1);
// Gets the specific subtype
$fieldable = $field->fieldable;
I have four tables in database: groups, specialties, lessons, group_lesson. It's structures:
groups
id
specialty_id
name
specialties
id
name
lessons
id
specialty_id
group_lesson (UNIQUE INDEX lesson_id, group_id)
lesson_id
group_id
date
My models look like that for now:
class Group extends Eloquent {
public function specialty() {
return $this->belongsTo('Specialty');
}
}
class Lesson extends Eloquent {
public function specialty() {
return $this->belongsTo('Specialty');
}
}
class Specialty extends Eloquent {
public function lessons() {
return $this->hasMany('Lesson');
}
public function groups() {
return $this->hasMany('Group');
}
}
I need get additional fields in Group model look like that
Group - Eloquent model
name - string
lessons - collection of Lesson models of Group Specialty
date - date from group_lesson table
I've tried different relationships and combinations, but it's doesn't work. Please help me to write correct relationships.
You can use eager-loading to access relational data through relationships, and can even chain relationships further. As a rule of thumb, if you can draw a path to from 1 model to another through a relationship, you can eagerload all the relevant and relational data for that with chained eager-loads.
Laravel Eager Loading
As an example
$speciality_group = Speciality::with('group','lessons')->find($id);
Even though you are only getting a single instance of the speciality model, the related data is hasMany, meaning multiple records. You need to loop through these records using a foreach loop to access the relevant data for them, or alternitavely add additional closures in your initial query to load only a single related model.
foreach($speciality_group->group as $group)
{
echo $group->name;
}
You will need to do this for both instances where you want to display related information.
Table Structure:
games
id | name
awards
id | award name | game_id (fk)
Relationships
A game can have many awards.
An award has one game.
class Games extends Model
{
public $timestamps = false;
public function awards()
{
return $this->hasMany('award');
}
}
I need to get all the games out of my database. I do this using:
Game::all();
I then need to get all of the games out of my database but include data from the awards table.
I want to have an array which I can loop through to output the games, and if the game has an award - output this also.
What would be the correct eloquent statement?
Laravel's relations are brilliant for this kind of thing. Everything you have so far is on the correct path.
// Controller
public function index()
{
$games = Game::all();
return view('games.index', compact('games'));
}
// View
#foreach($games as $game)
{{ $game->name }}
#if(count($game->awards) > 0)
// Game has some awards, lets loop through them
#foreach($game->awards as $award)
{{ $award->name }}
#endforeach
#endif
#endforeach
Using the relation you've setup in your Game model you can instantly access the related data from other tables. Now each time you call $game->awards it will query the database, however using Laravel's Eager Loading you can pull all this information out at the same time rather than on demand.
// Controller
public function index()
{
$games = Game::with('awards')->get();
return view('games.index', compact('games'));
}
and by doing the exact same thing in the view you're no longer running a new query each time you want to get a games awards as they've already be fetched from the database. More on eager loading here http://laravel.com/docs/5.0/eloquent#eager-loading