I am a newbie at Symfony2 and just hacking my way through some existing codebase. I need help in understanding how to achieve the following outcome.
I have a PriceList entity that stores:
DollarCostPrice, PercentDiscount1, PercentDiscount2, PercentCommission
I want the user to enter following 4 values through a form:
SalePrice, DollarDiscount1Price, DollarDiscount2Price, PercentCommission
where,
SalePrice = (DollarCostPrice + (PercentCommission * DollarCostPrice))
DollarDiscount1Price = ((DollarCostPrice + (PercentCommission * DollarCostPrice)) * (100 - PercentDiscount1)/100)
DollarDiscount2Price = ((DollarCostPrice + (PercentCommission * DollarCostPrice)) * (1 - PercentDiscount2)/100)
But once user has entered the above values, I will compute DollarCostPrice, PercentDiscount1, PercentDiscount2, PercentCommission that need to be persisted in the entity.
I want to use a collection of above 4 fields in a form so that users can enter this information for multiple items at once.
I am still new with using forms, collections and data-transformers in Symfony2. I would appreciate if someone could help me determine the best way to do it.
I would do it this way. Note I'm making assumptions here, you haven't provided any information on what entities relate to PriceList for example.
Create a PriceListType form type, you would have 1 mapped field (PercentCommission) and 3 non-mapped fields (SalePrice, DollarDiscount1Price, DollarDiscount2Price)
To add a non-mapped field:
->add('SalePrice', null, array('mapped' => false))
I'm assuming that PriceList has a relation to some sort of SKU object or similar. You need to consider how you manage this relation.
Start with a basic form for your PriceList Type. I'd use something similar to this technique for adding new PriceList items.
On Save I would initially just do the entity calculations in the controller to get things up and running quickly E.g.
$yourForm->handleRequest($request);
if ($yourForm->isValid()) {
// loop through elements in your collection - note for non mapped fields
// you need to access them like so: $form->get("DollarDiscount1Price")->getData();
// calculate the data you wish to save to each PriceList entity, set and persist it
$entityManager->persist($priceListEntity);
// finish & finally flush
$entityManager->flush();
}
Then later I might move that functionality to a form event listener.
I don't see a need for a data transformer here myself as it'll add complexity. If you're planning to re-use this functionality in a number of forms maybe, but otherwise I don't think I'd worry about it.
People will tell you X is the ideal solution, but IMO start simple and refine as you go.
Related
I have a Entity in my database (say Member) which has many relationships with other tables (6 relationships to be exact). Some of them I don't want mapped with the ORM (I mean linked to this Entity) because they may have many records (like MemberAccessLogs for example) and some other load many other entities.
Now I want this Member Entity to have an isDeletable method so I can disable exclude button in administration page.
If I where to do this the traditional way, I would have to declare the associations with all the other tables in the entity class, including MemberAccessLogs and I would put the method in it so I could test if these associations are empty.
But AFAIU, I would have to make a fetch (or at least a count) to the association's tables in order check for empty.
Another way would be to fetch the Members I want shown and then make a separate query to check for empty with a low cost exists(select * from table limit 1) in these sub-tables and then populate the isDeletable method in Member programmatically before pass it to Twig.
But I found this solution cumbersome. Anyone has a better way to do this ?
Just for the record: Some people may think this is "premature optimization". I maintain (contrary to some), that you should think ahead when you are programming and don't this this is bad. But I really think this isn't the place to discuss it. Please let's focus on the question asked ok ? :)
Edit
To easily prove that limit 1 is increadibly faster than count, I did a small test in a table in my database that has more than 20 million lines. Here are the results:
select count(*) from loga [20 million+ table]
20678473
1 row(s) fetched - 27023ms
select exists(select null from loga limit 1)
true
1 row(s) fetched - 2ms
I guess 13511,5 times faster is conclusive enough. :D
Extra lazy
You could look into extra-lazy associations.
Basically you map all associations as you normally would, and add fetch="EXTRA_LAZY":
/**
* #Entity
*/
class CmsGroup
{
/**
* #ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
*/
public $users;
}
Now Doctrine will not load the complete collection into memory the first time it's accessed, but performs specialized queries to load the parts you actually need at that moment.
So $users->count() (or count($users)) on the collection would trigger a simple count-query in stead of loading the complete collection into memory.
PostLoad
You could use an postLoad event to determine if such an entity is deletable. This postLoad event is called after an entity is constructed by the EntityManager, so when the entity is loaded.
Add an unmapped property ($isDeletable) to the entity that stores whether the entity can be deleted or not.
Create an entity listener that listens to the postLoad event. The listener can have the EntityManager, DBAL Connection, or anything else injected. With that dependency you could perform whatever query you want and use the result to set $isDeletable.
The result is a single additional query when the entity is loaded, after which the entity "knows" whether it's deletable or not.
An example of using the postLoad event can found in a Cookbook entry on the Strategy Pattern
Do note that when the conditions that determine whether it's deletable or not change, the value of $isDeletable could become incorrect. To resolve this issue, you could keep track of those conditions:
Keep track
Add a mapped property ($isDeletable) to the entity that stores whether the entity can be deleted or not. It would probably start with true.
When something is added to an association which would mean that the entity is no longer deletable, set $isDeletable to false.
When something is removed from an association that which would mean that the entity is deletable again, set $isDeletable to true.
In other words: with every change you keep track of whether the entity is deletable or not.
This way you won't need any additional queries at all.
There's a Cookbook entry on aggregate fields that explains this concept very well.
Is it possible to collect and map two fields from form to one property?
Details:
I have an array field in my entity:
#ORM\Column(name="custom", type="simple_array")
where admin can specify (multi choice) custom options for the product - for example:
$product->setCustom( array('customText', 'customNumber') );
So the user should have two fields in his order form - text and number. Then I want to save them:
$order->setCustomOptions(array(
'customText' => 'Foo',
'customNumber' => '100',
));
In order entity there is just array field type instead of simple_array.
User can't add new options so the collection field type isn't a good choice in my opinion.
I have also tried to use data transformer but I think it can be applied only to one field.
Tell me if it's not clear. I don't need a complete solution but a hint what to choose.
Finally, I created two additional fields in my Entity (like DonCallisto suggested) without mapping to Database and rendered them in the form. Then instead of using DataTransforem, I used a setter with #ORM\PrePersist and #ORM\PreUpdate to set custom values together.
I am new to Symfony2 and building an app based on an existing legacy MySQL schema. I've become familiar with all the Intro docs (The Book etc) but still needing to inderstand some higher level concepts of how to properly use the framework. Trying to get my head around the concept of an entity in terms of how I normally would go about writing SQL queries. I've used the CLI to generate entities for all my existing tables. As an example ... there is a Clients and a Titles entity already. Titles are 'owned by' Clients and the core Symfony annotations have mapped them correctly.
So, given a titles table with many columns of values but only one titles.client_id ... say I want to create a form action in the ClientsController (clients.yml route: /clients/{id}/add_title) that for the given client id in the url will allow the user to enter a title name and have it save a new record into titles with only the titles.name & titles.client_id values ... very simple really.
My question is ... in defining this very simple query (in normal SQL)
INSERT INTO (titles) VALUES (name, client_id)
DO I need to create another entity for titles JUST to work with those 2 specific values?
OR
What is the ideal way to use part of an entity for a specific repository ... in this case just a subset of the titles table (name & client_id)?
Here is the Action method in my Clients Controller:
//use Entity & Form namespaces for BOTH tables;
public function addTitleAction(Request $request)
{
$client_entity = new Clients;
$titles_entity = new Titles;
// generate simple 2 input form with Form\TitlesType
return etc ...
}
You may be able to tell, I also need to figure out how to work with the Form classes but my basic question here is how to generate simple queries from larger Entities and how to call from the Controllers of another Entity/Table Controller. Thx for your help.
To wrap your head around the new concepts, think of an entity as a row returned from your table. Think of a repository as your queries on the table. So you should have a Title entity (not Titles).
INSERT INTO (titles) VALUES (name, client_id)
DO I need to create another entity for titles JUST to work with those
2 specific values?
You'll want to create a new object when creating a new record (think of the new object as a new record that you then save), along the lines of:
$title = new Title();
$title->setClient($client);
$em->persist($title);
In Symfony2, if I embed a collection of forms pointing at a many to one relationship in Doctrine and allow adding and deletion, if I delete a record from the beginning, add one at the end, and edit some in the middle how does the system know which records to update with which data?
There is nothing in the tutorial that passes the primary key of the embedded data around. Under certain circumstances, my records are getting needlessly deleted and added again rather than edited in place (even if there are no changes to the particular record). This breaks the fields on the records that are not included on the form, setting them to their default values from the DB model.
Is there a way to pass the primary key in the form and have it used to perform updates when the data comes back?
If you want to index the collection (by the entity id) for all querys, you can simply use the indexBy annotation in your entity class.
/**
* #ORM\OneToMany(targetEntity="EntityClass", mappedBy="EntityVariable", indexBy="id")
*/
private $collection;
Based on the Akkumulator's answer and comment and some experimentation, I did this:
Create new fields (using Javascript as described in the documentation) with __name__ replaced not by a number but by a string: new_ followed by an forever increasing number that has nothing to do with the list (e.g. new_1, new_2, new_3...)
I don't have to push the primary keys into the forms and I don't need indexBy either - that's good, because indexBy felt like it was too far removed from the form, ending in having the Action at a distance anti-pattern.
Why this works:
PHP arrays aren't like those in other languages. They're always dictionaries, so you can add string keys to them even if they only have numeric keys to start with.
Because the Symfony collection is mapped by field name, new fields will not match existing data and deleted fields will not be matched to existing data (and thus be removed from the set)
One way to pass primary id is to use INDEX BY.
For example, say I have an entity called Customer and a Customer has several Emails. In my Customer repository class, I can specify my collection to be indexed by Email's primary id.
$qb->select('c, e')
->leftJoin('c.emails', 'e', null, null, 'e.id')
->where('c.id = :id');
By doing so, the generated name of the input tag would be
customer[emails][e.id][fieldName]
Upon submitting the form, Symfony will bind the request values according to the input names.
I'm new to zend framework but have made my first steps with it successfully.
Until now I have created some Zend_Forms which are mapping single records of my model
to the form fields. I have handled the forms with form classes for each case.
This works all very well until now.
Now I have the situation that I have to asign features to a product. Features and products are parts of my application. Features are stored in my database in three tables. For each feature there is one record in the third table.
First is the feature group where the name of the feature group is saved. Every feature should be asigned to a feature group.
Second table is the features table. This table has an foreign key to the feature group and the name of the feature.
Third table is some kind of many-to-many relation which connects features to products. This table has an aditional field which contains an optional value (beside the two foreign keys) for this unique feature of the product.
For example: if the product has a weight of 4,78 kg the value "4,78" is stored in the third table and the label "weight of %s kg" is stored in the second table. The feature group could be something like "physical attributes" had is saved in the first table.
To cut a long story short:
My problem is how to handle the case that I have to create and edit multiple database records in one form. The plan is to have a form with many checkboxes for each for a feature whereby features are thematicaly grouped. Every checkbox should have an aditional text field to input optional values.
you could make a custom form class that extends Zend_Form and use that for you classes.
It could take in the construct instances of your models and construct the form inputs based on that models.
After form validation in your controller you can do
$values = $form->getValues();
and use that array to populate your models again
You can try creating subforms (Zend_Form_SubForm) inside your form class. This can separate fields for different tables. For edition, in your controller, when you pull all the data from the tree tables, you can populate subforms that correspond to the tables.
You can try to extend Zend_Form to create your own elements.
You will be able to write a class that connects to DB to get attributes (features & products).
Assuming you wrote My_Form_Element_Features & My_Form_Element_Products classes, you can do $features = new My_Form_Features(); and then use the base class methods like getValues(), populate(), etc.
You can take a look there to start :
http://framework.zend.com/manual/en/zend.form.elements.html
http://smartycode.com/extending/database-aware-select-elements/
--
To answer to your comment, you can use :
Zend_Form::setElementsBelongTo($array):
More information can be found at Zend_Form Advanced manual page.