Nested Category display in Symfony2 with 4 level - php

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 ;) )!

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.

Symfony 3 / sonata_type_collection change query each add row

I'm using the Sonata Admin and currently trying to get me to be able to change my query to every click event in the sonata_type_collection.
I have the 'Variation' entity in which it contains the collection type in the form to call the entity 'Value'.
However, this 'Value' entity contains an auto relationship to reference parent values ​and child values ​ex: (Id 1 does not contain any parent, so its field will be null, but id 2 contains a parent value, putting id 1 in the parent field parent).
The idea was that with each click that was given to add a new row of values, check the previous row and perform a new query to bring only the "child values".
Take a look into the SonataMediaBundle and SonataClassificationBundle source code it seems there is a similar case like you described.

RESTful convention to manage items and types of items

I have a database with two tables: Type and Item.
Each Item has a Type (so in the Item table there is a field named type_id).
I need to create an REST api to manage those items but I'm struggling to define the URL structure for it.
My initial approach is:
POST /api/type Creates a new type.
PUT /api/type/id Updates an existing type.
Then for the items:
POST /api/items/typeId To create a new item and relate it to the type.
PUT /api/items/typeId/itemId To update the item.
The problem that I can see with this is, after an item is created, it looks like I cannot change its type, so I changed it to this:
POST api/type/typeId/item To create a new item.
PUT api/items/itemId To update an existing item.
But it doesn't seem right (lack of consistency?).
Any help, please? What is the convention to manage parent/children items?
Just because item is related to type, it does not live in a hierarchical structure below type, especially if the relation can be changed. See it as a flat structure:
POST /api/item
PUT /api/item/{itemId}
where the item's typeID is just one of the POST/PUT parameters.
Where it makes sense to use hierarchical URLs is for retrieving data. For example
GET /api/type/{typeId}/items
lists all items of this type.
Note: I used the singular form item instead of items in POST/PUT for consistency reasons. You only send one item at a time. While when retrieving, you retrieve multiple items.

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.

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