How to use a many to many relationship in Symfony2 - php

I am working on a Symfony 2 based web app and struggling to create a many to many relationship between to entities:
Each Task entity should be assigned to any number of Categories. Of course each Category can be used by any number of Task entities. While a Task needs to know its Categories the Category class has no relationship back to the Tasks.
Following this tutorial I created:
class Task {
/**
* #ORM\Column(name="categories")
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\Category")
*/
protected $categories;
public function __construct() {
$this->productVariations = new \Doctrine\Common\Collections\ArrayCollection();
...
}
}
Which results in the following SQL table:
CREATE TABLE task ... categories VARCHAR(255) NOT NULL, ...
A new Task is created use a form with the following type:
class TaskType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('categories', EntityType::class, array(
'class' => 'MyBundle:Category',
'choice_label' => 'name',
'multiple' => true,
'expanded' => true,
))
...
}
}
Submitting the form and persiting the new Taskinstance result in a SQL row with the following content in the categoriescolumn:
Doctrine\Common\Collections\ArrayCollection#000000007448a17a0000000048941f2f
What does this mean? Storing the referenced Category entities as array is not normal form but of course valid (in fact I would prefer this solution over adding a relationship table since in practice the number of categories per task will be quite small, thus a third table would be more overhead).
However I would assume that the array stores the IDs of the referenced objects/rows in some form. How would it be possible to store any number of references in a VARCHAR(255) column?
While submitting and persisting creates a DB entry, the Category references are not stored correctly. When I try to re-read this entry, the categories property is empty.

You have redundant column definition. Try to simply remove following line:
* #ORM\Column(name="categories")
Then you need to update you database schema.
This line creates a varchar field in task table which shouldn't be there. Many to many relations are implemented via separate table.
If you want to store ids in a single field instead of separate table, then you will handle it on your own. That's because it won't be able to be a relation anymore. You loose possibility to create foreign key or doing any join queries. Also filtering records will be harder to achieve.

I think you're missing something here.
Like you said, a Task can be associated to many Category(ies) AND the Category class DOES NOT need a relational mapping to the Task class.
All you need in your Task class is a property to reference your Categories.
I think what you're looking for is this.

Related

PHP Symfony FormType EntityCollection into Choices

I am beginner in PHP and Symfony and I started recently on a project.
We have 2 entities which are related by doctrine Annotation ManyToMany with JoinTable
Example: entity and datatable Room, entity and datatable Person and datatable RoomPerson (entity does not exist and just exist in doctrine annotation JoinTable).
The entity Room has a property Collection of Persons.
With a formType it is rendered in page as a select html element multiple (so we can choose several persons)
FormType with preEvent to fill list of possible persons:
$allowedPersons = $this->em->getRepository(Person::class)->findBy(***);
$form
->add('personList', EntityType::class, [
'class' => Person::class,
'choices' => $allowedPersons ,
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Room::class,
New request is to order the list of Persons.
So I made changes described in some tutorials to change declare the Entity RoomPerson et remove annotations ManyToMany for ManyToOne and OneToMany. I added the property "Order" on RoomPerson.
I Added the methods AddRoomPerson and RemoveRoomPerson to make updates of the list by the mapping of my FormType.
So my entity "Room" now only contains a property Collection of RoomPerson.
But I don't know how to use it in my FormType because I always want to show the list of Persons.
I tried a lot of things without success.
example:
$allowedPersons = $this->em->getRepository(Person::class)->findBy(***);
$form
->add('roomPersonList', EntityType::class, [
'class' => RoomPerson::class,
'choices' => $allowedPersons ,
'choice_value' => function(?RoomPerson $roomPerson) {
return $roomPerson? $roomPerson->getPerson()->getIdPerson() : '';
},
'choice_label' => function(?RoomPerson $roomPerson) {
return $roomPerson? $roomPerson->getPerson()->getName() : '';
},
I get following error:
Argument 1 passed to App\Form\RoomPersonType::App\Form{closure}() must be an instance of App\Entity\RoomPerson or null, instance of App\Entity\Person given, called in ..\vendor\symfony\form\ChoiceList\ArrayChoiceList.php on line 200"
So please I need help to transform my list of RoomPerson in my formtype and to make an update of this list in page.
When you are creating a form, the ->adds will make a reference to different columns you have defined in your App\Entity.
So, if you want to add something to your form this way, it has to be represented, again, in App\Entity.
Now, what you want to do, AFAIU, is fill a Room with people (class Person), and keep a list of those in the room (RoomPerson), and also a list of where every person has been (that's why it is a ManyToMany).
If instead you are looking at only where a Person has been the last time or at the moment, then it is a OneToMany relation, cause a room would be able to hold a bunch of people but any person would only be able to be at one place at a time (obviously).
Now I will simply take for granted you want to achieve the ManyToMany case. You should have all the columns needed in both Room and Person.
Now, RoomPerson should simply have a connection of those two and it's own id. You can add more things but for the purpose of this example let's just suppose we just need it to be a simple relating table so... your tables would look like (as an example):
<-M:N->
Room: <-M-> RoomPerson <-N-> Person:
- id (pk) - id (pk) - id (pk)
- space - room_id (fk) - gender
- style - person_id (fk) - room_usage
- purpose - age
- etc etc... - etc etc...
M being M number of posible objects on one side, N being N number of posible objects on the other side.
Once you have a DB like this, you can simply create a Form where you pass a Room, with every row you need in it.
If what you want is to select a room and have all the people displayed, you can, on the controller-side, get via queries a list of all the people that have been in each room, send it to the form, and then, on your template-side, play with html and js to dinamically show the correct list in each room.
I would need a more precise description to know exactly what you are trying to accomplish.

Multiple entities in one form [Symfony 3]

Here are my tables:
Movie
id
name
year
description
Person
id
name
date_of_birth
Role
id
name
movie_id
person_id
So, Movie should contain, obviously, movies. Person consists of people (actors, directors or writers). Table Role connects with other 2 tables by having $movie and $person variable inside its class, with Many-to-One relationship for both of them, which in the end results in movie_id & person_id.
What I want to achieve is having a form, where I will be able to insert new movie, but also its relevant actors, director(s) & writer(s).
Final form result should contain these fields:
movie_name, movie_year, movie_description
dropdown with list of persons from DB (person.name)
dropdown with 3 possibilities - actor, director, writer (which would eventually be inserted to DB)
So, after filling out the first three inputs, user chooses a person from a dropdown list, and then, chooses appropriate role for chosen person. If I'm thinking right, I should be able to SELECT from Person (name) table , and to INSERT into Movie (name, year, descr.) & Role (name, movie_id, person_id) tables.
I'm aware that I should be using Form collections, but I don't understand how to use them, at least in this situation. I guess I should start by creating something like RoleModel class, but not sure whether I made a mistake before going into this, which makes this problem more complex than it should be.
My questions really is - how to make this work with Form collections?
I can provide any necessary code, if needed.
Symfony make it really easy, so first, you need to add bidirectional relation (One-to-many) between your person entity and your movie entity so you need to:
Add property called person to your movie entity:
Code:
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Person", inversedBy="movies")
*/
protected $person;
Add property called movies to your person entity:
Code:
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Movie", mappedBy="person")
*/
protected $movies;
Now in your MovieType file (form configuration file) you can do it by:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('year')
->add('description', TextareaType::class)
->add('person', EntityType::class, array(
'class' => Person::class, //This existed usually in (AppBundle\Entity\Person)
'choice_label' => 'name'
));
}
This is a simple demonstration on how to use EntityType in Symfony.
Good luck.
You can solve this by embedding FormTypes, you can create a new one which is a composition of a PersonFormType and MovieFormType, and it would need an extra field for the role type (director, actor, etc)
Then, after submit, you can retrieve your objects something like this:
// SomeController's action
...
$data = $form->getData();
$person = $data['person'] // or whatever is the name of the field
You can find more information about embedding FormTypes in the docs: https://symfony.com/doc/cur...

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)

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

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.

One-to-one association in form?

In symfony 2.0, how to create a drop down list using one-to-one association in form? Can you guys put good example please?
I will try to answer your question the way I understand it. Let's say I have a Faculty object bound to a single University object. So in the form used to create or edit a faculty, I display a combo box of all the university in the database and the user choose one among them. There is one special Symfony field type that does exactly this: the entity type. Below is the code of the buildForm method that I use in my FacultyType object used to create the faculty form:
// Application\AcmeBundle\Form\Type\FacultyType
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('name');
$builder->add('university', 'entity', array(
// The class of the entity used as a combo box item
'class' => 'AcmeBundle:University',
// The property of the entity displaying the entity as text
'property' => 'name',
// The query builder used to populate the combo box, accepts
// a QueryBuilder object or a \Closure like below
'query_builder' => function(EntityRepository $repository) {
// This will return a query builder selecting all universities
return $repository->createQueryBuilder('u');
}
));
}
Note: There are other properties that can be set for the entity field type, I invite you to take a look at this page for more information on it.
Rendered, this will show a combo box with all the universities I have set in the database. When the user save the form, the university chose is assigned to the faculty object bound to the form via a setter. You could probably render a drop-down list instead of a combo box. If you need to select multiple entities, the 'multiple' option of the field type entity could be useful.
This being said, the example I showed is not a One-to-One relation but rather a Many-to-One for the Faculty object and a One-to-Many for the University object. A One-to-One relation would be something more like a relation where a University has a unique Address. In this case, a combo box wouldn't be useful since the university can only have one adress so a sub-form would be more appropriate. If it has many adresses, then it becomes a One-to-Many relation like the relation between the university and its faculties.
Not sure if this will answer your question correctly but I hope it will lead you to a final solution.
Regards,
Matt
You need to use the entity field type in Symfony2. A good example is found at http://symfony.com/doc/current/reference/forms/types/entity.html

Categories