I want to add an attribute on a pivot table on every attach/save in a belongsToMany relationship.
Example:
// I don't want to do add the `rand` attribute everytime...
User::find(1)->roles()->save($role, ['rand' => rand()]);
User::find(7)->roles()->save($role, ['rand' => rand()]);
User::find(42)->roles()->save($role, ['rand' => rand()]);
// ... I just want to call save...
User::find(1)->roles()->save($role);
// ... and magically, `rand` attribute is set to rand()
I wanted to overload belongsToMany function in Model, but it does not look like to be a good solution, it looks like a hack. Because doing this, I have to create a new BelongsToMany class extending the original one, call the original attach method in Model, then override the returned object, and then my code becomes spaghetti, so I threw everything.
Is there any elegant way to do this?
Side note: rand attribute is a stupid example, it's just an illustration, don't care about it.
create saveRole method in your User model
public function saveRole($role)
{
$this->roles()->save($role, ['rand' => rand()]);
return $this;
}
then just call
User::find(1)->saveRole($role);
I think it's much more easier and clean then overloading BelongsToMany class
Related
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 there any possible way to lazy load a custom attribute on a Laravel model without loading it every time by using the appends property? I am looking for something akin to way that you can lazy load Eloquent relationships.
For instance, given this accessor method on a model:
public function getFooAttribute(){
return 'bar';
}
I would love to be able to do something like this:
$model = MyModel::all();
$model->loadAttribute('foo');
This question is not the same thing as Add a custom attribute to a Laravel / Eloquent model on load? because that wants to load a custom attribute on every model load - I am looking to lazy load the attribute only when specified.
I suppose I could assign a property to the model instance with the same name as the custom attribute, but this has the performance downside of calling the accessor method twice, might have unintended side effects if that accessor affects class properties, and just feels dirty.
$model = MyModel::all();
$model->foo = $model->foo;
Does anyone have a better way of handling this?
Is this for serialization? You could use the append() method on the Model instance:
$model = MyModel::all();
$model->append('foo');
The append method can also take an array as a parameter.
Something like this should work...
public function loadAttribute($name) {
$method = sprintf('get%sAttribute', ucwords($name));
$this->attributes[$name] = $this->$method();
}
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.
Using CakePHP 1.3, is there is a callback that is fired after a saveAll() on a model, or a way to implement such a behavior?
Maybe afterSave() already does this?
Specifically, I would like to run a couple particular methods, but only after related items have been saved, and only if the parent item is a newly saved instance.
Something like the $created argument, passed to afterSave(), obviously seems perfect, but I'm at least 90% certain that afterSave() is called on a model after the initial save -- which I understand has to happen before the related models are saved (so that they have something to put in the FK field).
What do you suggest for obtaining this behavior?
There isn't a callback for Model::saveAll() built into CakePHP, but I believe you can override that method on the model to create your own, like so:
// In your Model class...
function saveAll($data = null, $options = array()) {
parent::saveAll($data, $options);
$this->afterSaveAll(); // Your new custom callback.
}
function afterSaveAll() {
// Callback code.
}
I am not currently sure about how to produce a $created variable behavior similar to what Model::afterSave() has, however.
afterSave() just like save() ...
its called for each model, saveall is just a foreach with save() so the afterSave will be called in each model that the final save() is called in
Can't you just do something like this:
if($this->Recipe->saveAll($this->data)) {
//Do some stuff and checking for new insert.
$this->Recipe.doSomeStuff();
$this->redirect('/recipes');
}
Perhaps you can tell that it's a created item because you won't be passing an id. I don't know because you haven't posted any code.
Imagine I have a table called "item" that has a column called "price". In addition to the full price, I'd like to get the price spread across 12 months, i.e.,
class Item extends Doctrine_Record {
...
public function getMonthlyPrice() {
return $this->price/12;
}
}
Now, say I'd like to have Item act like the monthly price is just another column rather than a function call, e.g.,
$m = Doctrine_Core::getTable("Item")->find(1);
echo $m->price; //prints 120
echo $m->monthlyPrice; //prints 10
My first instinct is to override the __get() method. Is there a better or more standard way to do this in Doctrine?
Bonus question:
Is there some very clever way I can rig the object so when I do
var_dump($m->getData())
I see
array
'price' => 120
'monthlyPrice' => 10
That would be pretty nifty.
I think what you need is Record Filters. I don't really know Doctrine, I primarily use Propel myself; but this sounds like it might accomplish what you need:
http://www.doctrine-project.org/projects/orm/1.2/docs/manual/defining-models/pl#record-filters
As Brian said you could do this with filters, but there is another way which I think is closer to what you're looking for - you'd need to enable Doctrine's ATTR_AUTO_ACCESSOR_OVERRIDE feature, which means that it will check for methods in the form get* and set* which match your property call them automatically. Here's an example:
class Example extends Doctrine_Record {
public function getFoo() {
return 'foo';
}
}
Doctrine_Manager::getInstance()->setAttribute(Doctrine_Core::ATTR_AUTO_ACCESSOR_OVERRIDE, true);
$example = new Example();
echo $example->foo;
As to the second part of your question, not that I've seen - your best bet is to override the method in your models as appropriate, or create a subclass of Doctrine_Record that your models then extend which has a single override that wraps up that functionality.