Fetch a Model's associations in Laravel to simplify creation/update - php

I have a Movie model with the following associations (belongsToMany):
Actor
Country
Genre
...
When a form is posted, I have this data (skipping a lot of details here):
'actors' => array(
'Cary Grant',
'Grace Kelly',
...
),
'genres' => array(
'Drama',
...
),
...
I'd like my update()/store() controller function to easily associate these Models.
An actor with the name 'Cary Grant' may or may not exist and may or may not be already associated with the movie I'm editing. Also I could remove him from this movie, so I'd need to remove the association. Same with Genre and everything else.
So I thought I'd do a BaseModel and do all of this only once in there, like this:
1. get the Movie Model's defined associations.
2. check if POST data contains those associations.
3. for each of them, check if they exist (if not create them) and return an array of ids. the column I'm checking is 'name', but it could be configurable.
4. sync() those ids.
For now, I don't need to add more stuff to the related model from the movie form (ex. an actor's birthdate).
I'm stuck at n.1 ($movie->getRelations() only works for existing movies) and in general I'm not sure if this is the right approach. Hints?

use direct relations: $model->actors
http://laravel.com/docs/4.2/eloquent#relationships
All sync() stuff will be done by ORM, so the most complex thing you should do is n.3. You must create diff between $model->actors and $_POST['actors'] and understand what actors you need to insert (create new actors).

I've met the same problem and this is what I do in my project:
Instead of retrieving all defined relations of the models, I white-listing relations that can be updated by adding a static member
Movie::$editableRelations = ['actors', 'genres', 'countries'];
Loop through the post data, and match with the $editableRelations arrays. If the data of a relation exists, following below steps, otherwise we simply do not touch this relation.
Step 3 and step 4 are the same as yours.

Related

cakephp3 link one table to multiple tables depending on type

So here's my problem.
I need to link an insurance policy to the insured property/item. Now the details vary greatly from car policy to a house or business one. So what I want to do is have something like this on the policies table
Policies
item_id
item_type
and that links to different tables depending on the value of the field "item_type" for example:
item_type = car then link to the cars table
item_type = house then link to the houses table
item_type = business then link to the businesses table
and so on...
I can do that on my own with php and mysql but I want to know the proper way to do it using CakePHP's table relationships and linking. I tried using the through option and a relationship table but it's not the same. Any ideas? or if a relationship table is the only way to do it then tell me how please.
This is actually a lot simpler than it first appears. I've done this a few times so I'll detail the technique that I use.
The first thing to do is create a behavior. This will allow you to enable any Table class in your application to have a policy attached to it.
The behavior is very simple. I've changed it to match your example, as I understand it. I'll talk through it after the code.
namespace App\Model\Behavior;
use Cake\Event\Event;
use Cake\ORM\Behavior;
use Cake\ORM\Query;
class PolicyBehavior extends Behavior
{
public function initialize(array $config)
{
parent::initialize($config);
$this->_table->hasMany('Policies', [
'className' => 'Policies',
'foreignKey' => 'table_foreign_key',
'bindingKey' => 'id',
'conditions' => ['table_class' => $this->_table->registryAlias()],
'propertyName' => 'policies'
]);
}
public function beforeFind(Event $event, Query $query, \ArrayObject $options, $primary)
{
$query->contain(['Policies']);
return $query;
}
}
So the in the initialize method we need to create a relationship to the table we attached the behaviour to. This will create a Table hasMany Policies relationship, meaning that any item in your system can have many policies. You can update this relationship to match how you're working.
You can see that there are a number of options defined in the relationship. These are important, as they link the tables items together. So the table_foreign_key is a field in your policies db table used to store the primaryKey of the related item. So if you're attaching a Policy to a Car, this would be the Car.id. The bindingKey is the key used in the Policy table to join on.
In order to filter the different types of attachments, you need the table_class field in your policies db table. This will be the name of the attached table class. So Cars, Cats, Houses etc. Then we can use this in the conditions, so anything pulling the primary table class will automatically filter the related Policies to match.
I've also configured the propertyName, this means that any item you look for which contains Policies will have an entity property called policies with the related data inside.
The last function in the behaviour is the beforeFind, this just ensures that whenever you look for the primary table class, you always return the related policies, you don't have to use this if you don't want to, but I found it handy to always have the related data in my use-case.
So then, how do we use this new behaviour? Just attach it like you would any other behaviour, and that's it. $this->addBehavior('Policy').
Be aware
This just reads data, you'll need to ensure that you save the table alias, and the foreignKey into the related table when creating new items.
Just for clarity, your policies table schema will need, at a minimum.
policies.id
policies.table_class VARCHAR(255)
policies.table_foreign_key INT(11)

Nested Category display in Symfony2 with 4 level

I have following table structure store category
When I wiil start to create my form, I have add first dropdown for lvl 1 category by default, now i want to another dropdown for sub-category which based on category, then I want 3rd lvl sub-category which are based on 2nd lvl selected sub-cateogry list.
I have only store 4 levels, Any Idea about how can build this structure on Symfony2.3?
Any Possibility to make above functionality manage in symfony2.3 or need to change in SQL table structure?
If you're using an entity to access your table, this extensions seems to be made for what you're trying to achieve:
https://github.com/Yavin/symfony-form-tree
You might have to modify your table structure slightly, though.
If you're sure you'll never have more than 4 levels, you could have 4 nested foreach cycles and produce an array similar to:
$categories = array("Electronics" =>
array("Television" =>
array("LED" =>
array(7 => "X SERIES",
8 => "TRILUMINOS")
)
)
);
and then pass it into your form like so:
$form = $this->createFormBuilder()
->add('category', 'choice', array(
'choices'=> $choices
))
I would solve it like this:
don't save redundant data or optimize, so remove 'level' and let's solve it for any depth. Limitations are often more complicated and bad code than solving the whole problem. If you don't want more levels, than just check the level when inserting a child, but when designing the model/entity layer, think about 0,1,n relations.
save the parent id as nullable integer foreign key, so a root category is defined by having parent id NULL and a leaf is defined by not being parent to any other category. This 1:n relation means parent id is the owning side, and a pseudo field 'children' (ArrayCollection) is the reversed side, that's doctrine.
The idea:
The form and request data is just the 'current category'. There is only one path from the current selected category to its root, so we don't need more data.
The validation of the form is simply a check: if category is a leaf, than the form is valid.
The model-view transformer takes a category and transforms it into its children. NULL at first is transformed into the root categories (something like CategoryRepository::findRoots()), and any valid category is transformed into its children (e.g. Category::getChildren()). You have to declare your transformer as a service since it depends on the database.
So use 'entity' as form type so we don't need to write our own view transformation. The entity form type is actually a choice form type and takes an array of items, but is now taking a single category and showing its children.
And please don't use any validation logic in the controller. An action like http://symfony.com/doc/current/book/forms.html#handling-form-submissions works for you. Let the isValid method invoke your validators via a constraint like #Leaf in your data object (like this example: http://symfony.com/doc/current/book/forms.html#embedding-a-single-object).
And don't forget
to hide items with status = 0
remember maybe there is a parent with all children having status = 0, so check that there are no children with status != 0 and not just if it's empty
There are so many exception when dealing with forms. I really recommend to write tests (but also for other parts ;) )!

Multiple Reference To Same External Table in Yii

I'm quite new to Yii development and I find myself stuck at the very beginning..
In my project I have a table with two columns both pointing to an external table.
Something like:
table "item":
- user_id_1
- user_id_2
table "user":
- id
So an item i always associated with two users, but I do not want to differentiate them, and I want to be able to do something like
$item->users
getting both the users.
And also the opposite:
$user->items
getting all items that reference the user id in user_id_1 or user_id_2.
Does this sound right? Any suggestion?
Yii's ActiveRecord is really only set up to handle one-to-one and one-to-many relationships - you seem to be describing a one-to-two relationship.
One way to handle this would be to set up a MANY_MANY relationship, but just use it with two users instead of "many". You will need to set up a user_item table that connects Users to Items, instead of embedding the key directly in the tables. If you need to enforce only two Users-to-an-Item you'll need to write some extra Validation code in your Item model. The relation in Item will look something like this, and you will be able to call $item->users like you want:
public function relations() {
return CMap::mergeArray(parent::relations(),array(
'users'=>array(self::MANY_MANY, 'User', 'user_item(item_id,user_id)'),
));
}
The new associative table will look like this:
user_item:
-- user_id
-- item_id
If you really need to use two separate user_id foreign key fields like you have here, you will need to set up an ActiveRecord HAS_ONE relation for each, like so:
public function relations() {
return CMap::mergeArray(parent::relations(),array(
'user1' => array(self::BELONGS_TO, 'User', 'user_id_1'),
'user2' => array(self::BELONGS_TO, 'User', 'user_id_2'),
));
}
But, you can still enable the $item->users call syntax with a function something like this, which will return both users in a single array:
public function getUsers() {
return array(
$this->user1,
$this->user2,
);
}
This takes advantage of Yii's __get() method override, which will allow you to call $item->getUsers() like $item->users.
You can set up something similar in the User model, with two HAS_MANY relations for user_id_1 and user_id_2, and a getItems method which returns the merged result arrays.
But I think the easiest option will be to set up a MANY_MANY relationship (and use it a ONE_TWO relation).

Proper CakePHP model to natively use multiple select and "dynamic" multiple select (e.g. tags)

In my schema Test HABTM Variable (originally I used hasMany but I found more documentation using HABTM even though it seemed overkill to me).
I want to use a multiple select in a CakePHP form and I don't want to have trouble saving, retrieving and pre-filling the data (ie. ugly array manipulations in beforeSave and beforeFind, extra queries for retrieving the selected options and other things that may cause me to lose my hair even earlier than I probably will anyway).
One usecase is a multiple select where the options are known beforehand,
the other one needs to allow for creating new and deleting old options (I'm using select2).
With the help of cake bake and some model HABTM documentation that was I missing when I read the FormHelper documentation I found out that
I have to name my multiple select form field like the model that belongs to it i.e. Variable.
It's implicit in the FormHelper documentation, but definitely could be highlighted more.
Also implicit: Because the find() operation gets the possible values for a field, I had to call the fields in my Variable model id and name (how else would it know from the Model name in the input call what to display). I can change find's options but that broke the convention at some other step I think.
Inconsistently if I want to supply a list of possible values in the controller, I have to set a variable that is lowercase, camelized and pluralized (not uppercase and singular like my Model and like I have to name the form field, not lowercase, underscore-separated and singular like my name field and my table).
I thought I didn't need to set the possible options in the controller (because I either know them or they're added on-the-fly by the user, I don't really want to populate the DB with them beforehand), but I tried to wrap my head around the convention. I could not get it to work unless I populated the DB with them beforehand and set them in the controller.
That seems fragile or at least more narrow compared to the treatment of single selects.
My continuing problems
With HABTM I can't create new options on-the-fly (it's okay for one usecase, but not for another, which is more like tagging).
With HABTM I don't want to have to populate the DB with options. And I don't really want to set the options in the controller, but in the view (because I anticipate this will cause problems with multiple "Tests" on one page, if it doesn't then it's okay).
With HABTM it doesn't work when I change the Form->input to Form->select (doesn't really matter, but adds to that feeling of my solution being fragile).
With hasMany I got as far as automatically selecting values I filled in in the DB, but it does not destroy or create associations (i.e. delete no longer selected options in the child table, even though it is declared as dependent). I think here it's due to the fact, that I don't properly pass the IDs of the child table, but how would be the Cake way to do that?
Sample code
/* Model Variable.php */
class Variable extends AppModel {
public $belongsTo = 'Test';
}
/* Model Test.php */
class Test extends AppModel {
public $belongsTo = 'Study';
public $hasAndBelongsToMany = array(
'Variable' => array(
'className' => 'variable',
'joinTable' => 'tests_to_variables',
'foreignKey' => 'test_id',
'associationForeignKey' => 'variable_id',
'with' => 'TestsToVariables',
),
);
}
/* in baked TestsController.php */
$variables = $this->Test->Variable->find('list');
$this->set(compact('variables'));
/* in edit.ctp, the baked view */
echo $this->Form->input('Variable');
Cake Form::input() has options for multi select, it also auto fills the values and selects previously saved data.
How does this not fit your needs, Have you even tried what is explained in the book?
http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#creating-form-elements

Storing sort order for items held in an HABTM association - CakePHP

In an ActiveRecord (CakePHP flavored) setup I have a HasAndBelongsToMany association setup on Videos and Bins: A Bin can store n references to Videos, and Videos can belong to n Bins.
I need to be able to manually set and store the display order of the Videos within a particular Bin (so the client can have his Videos in a particular order within the Bin.) My initial thought is to create a field in Bin that stores an array of Video IDs in the order they need to appear. Is this the most efficient way to handle this?
If so, when I then get the HABTM query result, what is the cleanest/most efficient way to re-order the returned query to match the sorted array of ID's?
The Videos associated with a Bin are returned as an array:
[Video] => Array
(
[0] => Array
(
[id] => 49b2de95-0338-48b9-bc88-01ba35664358
...
)
[1] => Array
(
[id] => 49b1ccad-b770-410e-be46-03a035664358
...
)
Or is there a better way to achieve what I'm trying to do without using HABTM associations?
Thanks in advance -
What to do when HABTM becomes complicated?
By default when saving a HasAndBelongsToMany relationship, Cake will delete all rows on the join table before saving new ones. For example if you have a Club that has 10 Children associated. You then update the Club with 2 children. The Club will only have 2 Children, not 12.
Also note that if you want to add more fields to the join (when it was created or meta information) this is possible with HABTM join tables, but it is important to understand that you have an easy option.
HasAndBelongsToMany between two models is in reality shorthand for three models associated through both a hasMany and a belongsTo association.
Consider this example:
Child hasAndBelongsToMany Club
Another way to look at this is adding a Membership model
Child hasMany Membership
Membership belongsTo Child,
ClubClub hasMany Membership.
These two examples are almost the exact same. They use the same amount and named fields in the database and the same amount of models. The important differences are that the "join" model is named differently and it's behavior is more predictable.
In your example, you need a way to add and remove without editing other users Video links, therefore standard habtm will not suit you very well. Create a model for this "join" similar to the Membership model described above. Further, if you added a weight field, you could use the ordered behavior (that I wrote) to order each set of videos per bin. Then you would use the following fields
id, bin_id, video_id, weight
And set up bin_id as the 'foreign_key' in the behavior configuartion. Good luck
Well I tried to solve just this problem and think I found the simplest solution possible:
When saving a new order of the related models you delete the existing relations and add the new ones in the desired order. This means the ids created run in your sort order and you can simply sort by id to display the order.
public $hasAndBelongsToMany = array(
'Item' => array(
'order' => 'ItemFoldersItem.id ASC, Item.name DESC',
)
);
Pretty simple isn't it?
Don't know about "most efficient" way to implement what you want, that depends on your whole application and what you want to accomplish. I'd just keep in mind that the most simple solution is often the best.
However, I can give you a tip for sorting your Video array! It is fairly easy:
$yourData = Set::sort($yourData, '/Video/id', 'asc');
Take a look at the Set class for more candy. :)

Categories