Consider the following example.
<?php namespace Example\Code;
use Illuminate\Database\Eloquent\Model;
class Profile extends Model {
protected $table = 'profiles';
protected $appends = [
'email'
];
/**
* Returns email_primary as email
*
* @return mixed
*/
public function getEmailAttribute()
{
return $this->attributes['email_primary'];
}
}
It is rather clunky and potentially inefficient for such a simple issue. A use-case would be if you have a legacy table that you are providing an API for and want specific control over naming conventions.
The following would be the proposed syntax for mapping.
<?php namespace Example\Code;
use Illuminate\Database\Eloquent\Model;
class Profile extends Model {
protected $table = 'profiles';
/**
* @var bool (optional) default false
*/
protected $mappedOnly = false;
/**
* @var array (optional) specifies columns to alias
*/
protected $map = [
'email_primary' => 'email'
];
}
It is similar to how doctrine works in ways while keeping the simplicity of eloquent.
The $map variable would take a key, value and treat it as column, alias.
The $mappedOnly boolean would allow for, if true, to _only_ select the mapped columns. This could help in certain situations where you need to load part of a table efficiently.
All in all, it adds power with a clean opt-in approach.
Thoughts?
Definitely cleaner.
+1
+1
Would come in handy in an application I am building right now.
It's clearer, but less powerful. Is this supposed to replace, or augment the accessor methods?
It is supposed to augment (and really be independent).
Here's an example:
profiles table
| id | primary_email | first_name | last_name | favorite_color | favorite_animal |
|---|---|---|---|---|---|
| 1 | [email protected] | John | Smith | blue | tiger |
| 2 | [email protected] | Susan | Banks | red | griaffe |
Profile.php
<?php
use Illuminate\Database\Eloquent\Model;
class Profile extends Model {
protected $table = 'profiles';
protected $map = [
'primary_email' => 'email'
];
protected $appends = [
'full_name'
];
public function getFullNameAttribute()
{
return $this->attributes['first_name'].' '.$this->attributes['last_name'];
}
}
LightProfile.php
<?php
use Illuminate\Database\Eloquent\Model;
class LightProfile extends Model {
protected $table = 'profiles';
protected $mappedOnly = true;
protected $map = [
'primary_email' => 'email',
'first_name',
'last_name'
];
protected $hidden = [
'first_name',
'last_name'
];
protected $appends = [
'name'
];
public function getNameAttribute()
{
return $this->attributes['first_name'].' '.$this->attributes['last_name'];
}
}
routes.php
<?php
Route::get('/profiles', function() {
return Profile::all();
});
Route::get('/light-profiles', function() {
return LightProfile::all();
});
GET: /profiles
[
{
"id": 1,
"email": "[email protected]",
"first_name": "John",
"last_name": "Smith",
"full_name": "John Smith",
"favorite_color": "blue",
"favorite_animal": "tiger"
},
{
"id": 2,
"email": "[email protected]",
"first_name": "Susan",
"last_name": "Banks",
"full_name": "Susan Banks",
"favorite_color": "red",
"favorite_animal": "giraffe"
}
]
GET: /light-profiles
[
{
"id": 1,
"email": "[email protected]",
"full_name": "John Smith"
},
{
"id": 2,
"email": "[email protected]",
"full_name": "Susan Banks"
}
]
For the "light profile", it isn't just "hiding" the extra properties, as you would do with $hidden. It is actually only selecting the "mapped" fields.
So, kind of like $visible then no?
@Anahkiasen well now the select will be constrained I'm thinking.
Anyway, I don't think this should be in the core. By using packages such as Fractal you have far more control over how your objects are sent as JSON and limiting the fields you retrieve from the database is already easily done.
The mapping in itself is something different, but I guess also easily implemented in a base superclass.
@JoostK — Is there really a simple way to limit the fields you retreieve the from the database? What if you want to have the contraint on all queries against that model?
On another note, it isn't really about the formatting for JSON. Its purpose is two-fold.
* is used in many places). Easiest way would be to define a VIEW in your database that only selects a subset of columns, although I don't know how this would impact performance.On 2, they both affect the select part of the query. That said, it does have deeper implications with how queries are built.
Though, this new approach would be (theoretically) more performant than any other approach mentioned.
Oh, I see, you meant to rename columns directly from the SQL. I had in mind that it was a mapping somewhere in Eloquent while setting attributes (much like your attribute setter but without that extra code)
Yeah, exactly. I'm trying to minimize the transformations done on the php side. There are, of course, places where it makes sense to transform a value with an accessor/getter, but it shouldn't have to be that way for a simple renaming of a column.
+1
It would definitely be nice to not need a custom DB query to specificy the columns to select on an Eloquent model.
@ardeay well you can do this, right?
Profile::all()->select('primary_email','first_name')->get();
but the purpose of this proposal seems to be more aimed towards the renaming of columns. seems useful, +1
:+1:
I like the idea of specifying aliases (similar to virtual fields in CakePHP), i.e. full_name may be the first_name and last_name attributes concatenated.
However, I _don’t_ like this “mapped only” flag. This sounds like something that should be in a service class, and not the model class for an ORM. The ORM should fetch and persist data to a data source, it’s then up to you what you do with those records once they’ve been converted to objects.
+1
@martinbean — Why would it be in a service? Isn't it the responsibility of the ORM? The hidden/visible approach is really just superficial mapping minus the ability to actually alias/map fields (which has to be done with getters/accessors).
@andyfleming Because the hidden/visible approach works well if you only need to access different attributes depending on two contexts, but fails when you have more than two contexts. Wrap it in an appropriate class that returns the attributes for that particular use case.
@martinbean — Yeah. I've done that in practice before, but it doesn't change that all the columns are being loaded behind the scenes.
@andyfleming Then that’s when you would specify the columns to load.
@martinbean But you need to do that on every query, right? What if I want a specific object to always load just those columns?
:+1:
+1 been. Exact same situation except it's the entire database!
-1 Not a fan of this. It adds a lot of complication into Eloquent for the sake of supporting 'legacy' applications to, essentially, prettify field names. With this, you now have 2 different field names in your application; one legacy/database one which may be used throughout your app and one that is used in eloquent and your models. If your aim is to make your application more simple and unify the field names, you've ironically done the opposite -- you've created another field name, adding another layer of complexity to your app. That is not good.
To me, if I was that fussed about changing field names in a legacy app, I would change them all by some kind of grep search / through migrations. Or, if it's not practical, use getter / setter attributes, which we already have. If your use case if for an API, format your response explicitly and just don't simply do toArray().
Also is this just for just 'getters' or should it work the other way with setting attributes, so when it comes to saving email in the database it get's saved as primary_email ?
Completely agree, @garygreen, and you explained it far better than me when I tried.
If you need to refer to columns by different names, then that’s what the Adapter pattern is for. Don’t stick it in your ORM for the sake of legacy. It just makes things convoluted and non-uniform.
If you need this functionality for a specific project it seems like it would be better if it was a separate package. Doesn't seem like it's something that would be needed on every project and thus included in Eloquent.
My use case is creating searchable json APIs for ugly, legacy dbs that I can't change. Something to this effect would be a useful package.
@KevinCreel Again: Adapter pattern.
I know this is closed but +1
I know this is closed but +1. If you are using laravel 5 this can solve your problems https://github.com/jarektkaczyk/eloquence/wiki/Mappable.
+1. I have an existing Laravel application that planned to have all 40+ tables migrated from an older schema, until we decided that was untenable in the near future. Making a translation layer through repositories and accessors works, but it's kind of a pain.
I would like to see some mature evolution in Eloquent, not some software I tryied to built at 17's with PHP 5.4. I'm sadly migrating to Doctrine right now.
Or then Laravel could be more like Symfony guys and use and help another mature ORM folk, could be Propel, which needs a lot more community and has awesome features.
You can use the packaged listed above for the mapping. I'm using it, and it works well. Still, it would be nice to have this in the core like other ORMs.
@mkrell If other ORMs do what you want, then use them.
@martinbean we are just dicussing here, looking for a better Eloquent. If you have nothing to add to the conversation, then please don't. Thanks for the adapter pattern btw.


Most helpful comment
@martinbean we are just dicussing here, looking for a better Eloquent. If you have nothing to add to the conversation, then please don't. Thanks for the adapter pattern btw.