Apply same accessor to multiple attributes in Eloquent - php

In eloquent we can cast multiple attributes to Carbon instances by specifying the dates in an array:
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
I have multiple attributes that I want to apply the same presentation logic to and instead of using multiple accessors (one method per attribute), I was wondering if it were possible to set up an array like the aforementioned dates array by which a particular method can be applied to.
So instead of having multiple accessors, like this:
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
public function getLastNameAttribute($value)
{
return ucfirst($value);
}
I could instead have an array like this:
protected $ucfirst = ['first_name', 'last_name'];
and then the ucfirst() function would be applied to the array by creating a method to do this.
Didn't see this in the docs and couldn't find anything from a search, but I may have been searching for the wrong thing.
Any solutions solely using eloquent would be great, I don't really want to pull in another package to do it if I can avoid it. Thanks!
Edit
Going to investigate how the $dates to Carbon works and try a similar approach, perhaps this is a starting point:
https://github.com/laravel/framework/blob/5.2/src/Illuminate/Database/Eloquent/Model.php#L2895

I don't know such possibility.
But you can encapsulate repeating functionality to some method and use this method in your accessors.

Related

Laravel model accessor with attribute name containing a number is ignored

In Laravel 5.7 in my database I have a property containing a number in its name - item_1_quality. When I create an accessor with method name using camel case it is ignored.
I have tried various combinations the most obvious of which was
public function getItem1QualityAttribute($value)
{
dd($value);
}
however, it does not work. I tried other possible combinations without success. I am properly calling the property as other accessors in the same model work fine. The problem seems to be related to the naming.
I have found a hacky but a working solution which someone might find helpful.
Add attribute to appends
protected $appends = ['product_1_quality'];
Create the following accessor:
public function getProduct1QualityAttribute()
{
$value = $this->attributes['product_1_quality']);
// do something with your $value and return.
}
The $value as argument will not work - access using the $attributes array.

Custom Field Name on Eloquent Model

I have a table posts and this table has some columns like pst_id, pst_title, pst_content, pst_category_id and like that. I wanna represent this fields with some better names on json output, actually I'm trying to remove the prefix pst_ from column names.
I tested multiple ways. At first I tried to make an alias for this columns on DB layer, for example Post::select(['pst_id as id'])->get(). This idea generally is awful, because it makes the column names inconsistent across of the software (each developer may have a convention to naming a field). So I insist to find a way for naming the columns on model layer.
The next solution was for using Accessors and Mutators. Although it covers the problem of previous way, but it's really hard to implement 20 methods for each model! 20x100 ~ 2000 methods!!! :/
The last solution which I tested was about using the mappable feature of https://github.com/jarektkaczyk/eloquence. It's really good, I can put all old fields to $hidden property and add new ones to $appends for showing on output. But this solution also have a problem. If I add all new fields to $appends, when I use select statement for choosing some columns, the non-selected columns will be showed on output with a null value :|. Well, I tried to override mappedselect and parseMappings methods on a base model for adding new names to $appends dynamically, but it doesn't satisfy me. In fact it becomes very tricky on using and I'm not sure that the team can accept it and use it easily.
So that's the problem: "Is there a way for renaming the name of columns on output for eloquent?". GoLang has a very good feature which is called Struct Tags. You can define some tags for your structure, for example like this:
type Post struct {
Pst_id int `json:"id"`
Pst_title string `json:"title"`
Pst_content string `json:"content"`
}
And when you produce a json for a Post structure with json.Marshal, based on tags, it gives you a json like this:
{
"id": 23,
"title": "Custom Field Tags for Eloquent",
"content": "I tried a lot of things, but they are hard. I'm a programmer so I'm lazy! What can I do?",
}
I think we don't have something like this in the php world, but is there any way to use the idea behinds doctrine's annotation for implementing something like tag structure in Go?
Any comments and idea are welcome!
First step would be to override a couple methods on those models. The first method is the getAttribute() which is called when you access an attributed of a model so you can access it. You would want to be able to access the attribute without the pst_ prefix so you would do:
public function getAttribute($key)
{
if(array_key_exists($prefixedKey = 'pst_'.$key, $this->attributes)) {
return $this->attributes[$prefixedKey];
}
return parent::getAttribute($key);
}
Then to make sure the keys don't have the prefix when casting to json you would override the attributesToArray() method which is what is called when outputting json and will also respect your $hidden, $visible, $casts and $dates arrays. That would be something like:
public function attributesToArray()
{
$attributes = parent::attributesToArray();
$mutated = [];
foreach ($attributes as $key => $value) {
$mutated[preg_replace('/^pst_/', '', $key)] = $value;
}
return $mutated;
}
To implement those you can extend the Model class with an abstract class that implements those methods and have your classes extend that base class or create a trait with those methods and have your classes implement that trait.
I would probably use Fractal Transformer from the League.
You basically create a mapping class and apply it to the collection.
The Transformer class would look like this
<?php
namespace App;
use App\Post;
use League\Fractal;
use League\Fractal\TransformerAbstract;
class PostTransformer extends TransformerAbstract{
public function transform(Post $post)
{
return [
'id' => (int) $post->pst_id,
'title' => $post->pst_name,
'content' => $post->pst_uuid,
];
}
}
Then in your controller or where ever you transform the collection.
$posts = Post::all();
$manager = new Manager();
$manager->setSerializer(new CursorSerializer());
$resource = new Collection($posts, new PostTransformer());
$formattedCollection = $manager->createData($resource);
The docs are pretty good and it is pretty straight forward to implement it.

Is it possible to get a collection of different model types for a model using eloquent?

If I have a model that needs to have a property that is an array of different models. Is there an eloquent method or way to handle this kind of problem?
eg.
I have a Feature model that needs a method that gets an array of objects that are from different models.
class Feature extends Model
{
public function getArrayOfDifferentObjects()
{
$array_of_objects=array();
???? ELOQUENT to get objects from different models ????
return $array_of_objects;
}
}
I have a feature_model_connections table with the following:
feature_id
featured_model_id
featured_model_type
The featured_model_type value would be a string denoting the model type.
The model_id would be a foreign key of the relevant model's table.
However I can't see how you would be able to use eloquent to return data for the getArrayOfDifferentObjects method in features model.
Any pointers would be much appreciated. Many thanks, J
What you are describing there, is basicly a Polymorphic Relations, which can handle these cases, and making fetching them easy, instead of i'm making a made up case, read the documentation, it is well written, under the section Polymorphic Relations. https://laravel.com/docs/5.1/eloquent-relationships#polymorphic-relations
Within your scope right now, you can do something like this.
public function getArrayOfDifferentObjects()
{
$objects = [];
$features = DB::table('feature_model_connections')
->select('feature_id', 'featured_model_id', 'featured_model_type')->get();
foreach($features as $feature)
{
$type = '\\App\\' . $feature->featured_model_type; //App is you app namespace
$model = $type::find($feature->featured_model_id);
if($model)
$objects[] = $model;
}
return $objects;
}
The basics of this, is you can define different types, with the app namespace seed, from there staticly call them, which will access the predefined type in your database table, then find the element and add it to the array. With that said, this is done as of the top of my head, no compile check, not ranned in Laravel, but it should pretty much get you the idea of what to do, with that said, if you can change your structure, go with the Polymorphic Relations, it is really awesome.

Laravel call all attributes at once

How can I call all of my models attributes including the ones defined as accessors/mutators?
At the moment I am creating accessors/mutators like;
public function setSatisfiedWithQualityOfCourseAttribute($value)
{
$this->attributes['group_answers']['satisfied_with_quality_of_course'] = $value;
}
public function getSatisfiedWithQualityOfCourseAttribute()
{
if (isset($this->group_answers['satisfied_with_quality_of_course'])) {
return $this->group_answers['satisfied_with_quality_of_course'];
}
return null;
}
And I can call them from my controller with something like $response->satisfied_with_quality_of_course but I need to be able to return all of them without explicitly calling them one at a time. Can I do that?
I need to call all "real" attributes and all "accessors".
You can use $response->attributesToArray() to get an array of all attributes, including your custom accessors.
You can use $response->toArray() to get loaded relationships as well, with their attributes.
If your custom accessors define new pseudo attributes (as opposed to overwriting existing database columns) you should also add them to the $appends array.
protected $appends = ['satisfied_with_quality_of_course'];

Hide columns when serializing via toArray()

I have a simple problem where I often return CRUD type Ajax requests with array serialized versions of Doctrine 1.2 models. I'd love to be able to simply return the toArray() method after the execute() result, however, this will display data about my models that I don't wish to expose. A simple example is on my user model the password and salt get displayed. While I realize those are already hashed values, it's something I'd rather not return as a JSON response.
I've poured over the Doctrine 1.2 manual, but did not find anything that offered the type of functionality I'm looking for. I realize I can iterate over the result to manually unset() the columns I wish to hide, but I'm hoping a more native solution is out there that I've overlooked.
Why don't you build your own toArray() ?
If you want to do that, you will have to extends the sfDoctrineRecord class that inherit from all Base* class. It is describe in the doc.
You have to put the configureDoctrine() inside config/ProjectConfiguration.class.php.
Then you will have a class like that:
class myDoctrineRecord extends sfDoctrineRecord
{
}
So you can easily add your custom toArray() here:
class myDoctrineRecord extends sfDoctrineRecord
{
public function toArray($deep = true, $prefixKey = false, array $excludeFields = array())
{
// do every thing like the original toArray
// but when a column match one entry in $excludeFields, don't add it
}
}
So, when using the toArray() method with an array of fields for the third parameters, they will be excluded from the result.

Categories