CakePHP form fields containing deep association not working until find() - forms

I'm writing my first Cake app and trying to set up my first deep association. It's very nearly working but I have a couple of small issues.
So I created a form to add a customer. A customer has many addresses and an address has many contacts. The issue I have is that the Form helper doesn't seem to recognise the contact fields for formatting purposes. However, if I perform a find() in the action the form displays correctly. It's like the model isn't linking until it's used.
Here's the 3 models:
class Customer extends AppModel {
public $hasMany = 'CustomerAddress';
}
class CustomerAddress extends AppModel {
public $belongsTo = 'Customer';
public $hasMany = 'CustomerContact';
}
class CustomerContact extends AppModel {
public $belongsTo = 'CustomerAddress';
}
Until it's submitted, the add action basically does nothing so I won't bother posting that, but here's some extracts from the view:
add.ctp
echo $this->Form->input('CustomerAddress.0.CustomerContact.title', 'options' => array( 'Mr' => 'Mr', 'Miss' => 'Miss', 'Mrs' => 'Mrs', 'Ms' => 'Ms', 'Dr' => 'Dr')))."\n";
echo $this->Form->input('CustomerAddress.0.CustomerContact.first')."\n";
echo $this->Form->input('CustomerAddress.0.CustomerContact.last')."\n";
.....
echo $this->Form->input('CustomerAddress.address1')."\n";
echo $this->Form->input('CustomerAddress.address2')."\n";
.....
echo $this->Form->input('type', array( 'label' => 'Business Customer?'))."\n";
So, the CustomerAddress fields work fine, they are the right type, limited length etc to match the database, but the CustomerContact fields aren't working at all. Likewise, if I submit invalid data, the add action does a saveAll and fails, but the form fields are then displayed correctly.
If I add a find() to the action before the page is displayed, they work fine. It's like the Model isn't being included until it's used.
I'm sure there's probably a simple command to get it to read the models or something but I'm a bit stuck with it.

ok, after much digging, I got this to work by calling ->loadModel on the controller. Since in this case I need all 3 models loaded in virtually every case, I added a beforeFilter to do this every time.
I didn't change the code for any models, but added this to the controller:
CustomerController.php
class CustomerController extends AppController {
public $helpers = array( 'Html', 'Form', 'Humanize');
public function beforeFilter() {
// Load associated models
$this->loadModel( 'CustomerAddress');
$this->loadModel( 'CustomerContact');
}
.....
}
Even though the CustomerContact model is loaded from the Customer model instead of the CustomerAddress model, Cake seems to respect the relationship and load it under CustomerAddress. At first, I tried:
$this->CustomerAddress->loadModel( 'CustomerContact');
but it was failing. Seems to work fine this way.

Related

Problems with Symfony embedded forms

I am trying to achieve the following scenario:
Auction and Category entities (many-to-one). The Category entity has a one-to-many relationship to CategoryAttribute entity which allows an unlimited number of attributes of various types to be added to the category. Let's say a Cars category will have Make and Year attributes. The CategoryAttribute entity has widgetType property which defines how to render the attribute (input, select etc.), attributeValues which is the data for the attribute (populating the select etc.) and isRequired property to tell whether a property is required or not. So far so good. Managing the attributes was piece of cake BUT:
On the Auction side of things I want when the user selects a given category from the list to render all the attributes for that category to be filled in. This is translated to an related AuctionAttribute entity (many-to-one to the auction in attributes property in the class). The AuctionAttribute has reference to the CategoryAttribute, a attributeValue to hold the input or selected value.
Well, the whole AJAX request and fill in of the attributes for a selected category was not a problem. The problem(s) arise when I submit the form. Basically there are two issues.
How do we bind the attributes part of the form to the actual form for validation. Let's say we have Car category selected and Make attribute is required, how do we validate that this attribute?
How do we bind the attributes input to AuctionAttribute entity in that form?
I know that for embedded forms I need to hook up to the FormEvents::PRE_SUBMIT event but I am not sure how to transform the attribute to an Entity in there.
In terms of code, I have the following:
When getting the attributes for a category, I create a AuctionAttributeFormType and render it into a twig form helper and return the HTML back in the AJAX request:
$form = $this->createForm(new Type\AuctionAttributeFormType(), null, array('csrf_protection' => false));
foreach ($categoryAttributes as $attribute) {
$form->add('attribute_'.$attribute->getId(), $attribute->getWidgetType(), array('label' => $attribute->getName(), 'required' => $attribute->isRequired());
}
When the Auction form is submitted, I hook to the PRE_SUBMIT event and when whether there is a attribute submitted and it belongs to the set of attributes of the category but this is as far as I went before I got stuck:
$builder->addEventListener(
Form\FormEvents::PRE_SUBMIT, function (Form\FormEvent $event) {
$auction = $event->getData();
if (null !== $auction['category']) {
$categoryAttributes = $this
->repository
->findAttributesForCategory($auction['category'])
->getResult();
if (count($categoryAttributes) > 0) {
$attribute_values = array();
foreach ($categoryAttributes as $attribute) {
if (isset($auction['attribute_' . $attribute->getId()])) {
$attribute_values[$attribute->getId()] = $auction['attribute_' . $attribute->getId()];
}
}
}
}
}
);
I need to get the values from attribute_values array into AuctionAttribute entities bound to the Auction entity. Any idea how this could be achieved. I think it should be done through some kind of data transformer but I am not sure to what to transform that data - should it be a form->add field, or directly touch the Auction entity which is filled in with data.
Any suggestions?
EDIT:
I made it work with the use of Model transformer but now there is another problem, when editing the record, if there is more than one attribute, only the first one is populated with data. Here is a sample gist of the code:
https://gist.github.com/SvetlinStaev/86e066a865478e40718c
My suggestion is NOT to convert the submitted data via Event Listeners, but to use a Data Transformer, which you attach to a form field like so:
$formBuilder->add(
$formBuilder
->create('FIELD_NAME', 'FIELD_TYPE', [
... FIELD_OPTIONS ...
])
->addModelTransformer(new SomeModelTransformer())
)
And the "SomeModelTransformer" class should look like this:
class SeatingToNumberTransformer implements DataTransformerInterface
{
/**
* Transforms the object from the norm data to model data
* The norm data is the field value. Say you have an integer field, $normDataObject would be an int.
* In your case: you need to instantiate several new AuctionAttribute objects and persist them maybe
*/
public function transform($normDataObject)
{
$transformedObject = $this->someTransformAction($normDataObject);
return $transformedObject;
}
/**
* Reverts the transform
* in your case: from AuctionAttribute to int
*/
public function reverseTransform($modelDataObject)
{
$transformedObject = $this->someOtherTransformAction($modelDataObject);
return $transformedObject;
}
}
More info can be found here
If you need more help, just let me know.

CUploadedFile::getInstance Always Returns Null on Uploaded File

I have built a form in YII and I need to process an uploaded file. I followed this guide, but I've stumbled upon an issue. CUploadedFile::getInstance always returns null.
Here's what I use. My model:
class AdditionalFieldFile extends CFormModel {
public $uploadedFile;
/**
* #return array validation rules for model attributes.
*/
public function rules() {
return array(
//note you wont need a safe rule here
array('uploadedFile', 'file', 'allowEmpty' => true, 'types' => 'zip,doc,xls,ppt,jpg,gif,png,txt,docx,pptx,xlsx,pdf,csv,bmp'),
);
}
}
And handling the uploaded file in the controller on form submit:
$model = new AdditionalFieldFile();
$model->uploadedFile = CUploadedFile::getInstance($model, 'field_'.$type_field.'['.$id_common.']');
And after that $model->uploadedFile is null for some reason.
Note that $type_field and $id_common come dynamically.
Also, the form has 'enctype'=>'multipart/form-data' so this is not the cause.
Ok, a self-answer follows.
It turns out that I had to use the active file field in the view, using an instance of the class that extends CFormModel as a model.
Solution:
CHtml::activeFileField(new AdditionalFieldFile(), 'field_'.$type_field.'['.$id_common.']');

cakephp and mongodb - relationships

i have a question on mongodb, model cakephp and relationships.
I'd create the following relations:
User -> hasMany -> City
City -> belongsTo -> User
In MongoDB, I have two tables:
users
cities (with key user_id)
In cakephp, I have 2 model:
User.php
class User extends Model {
public $name = 'User';
public $actsAs = array('Containable');
public $hasMany = array ('City');
..
}
and:
City.php
class City extends Model {
public $name = 'City';
public $actsAs = array('Containable');
public $belongsTo = array('User');
..
}
In my controller I use :
$user = $this->User->find('all');
but it doesn't work. In sql dump, cakephp uses a find only on tbl users.
Why? Where I wrong?
I normally place recursive to -1 and containable in app model, so it applies to all models you create unless you override specifically.
class AppModel extends Model {
public $actsAs = array('Containable');
public $recursive = -1;
}
Your relationships are fine, although I usually add className and foreignKey just to be safe and clear. In your controller you should do something like this:
$users = $this->User->find('all', array(
'contain' => array(
'City'
)
));
Recursive will prevent any associated records being included by default, this is good as sometimes you do not need the recursive data and extra data will help slow down your application.
Next adding contain into your find call may seem like a chore but it will be clear and concise what you are querying, any 3rd party developer will understand exactly what you are doing if they know how to use Cake. Hope this helps.

Zend Framework Structure for Datamapper, Controller and Model

I have just started with Zend Framework, so I have lots of questions about the structure.
I hope I can explain it properly, it's rather difficult.
Ok, I have done the Quickstart Tutorial and decided to use a similar structure for my first project.
So I have a Datamapper, a Model and the Database Table File.
I have created a Form where I can enter a some Information(Item) and upload Images with it.
I have 2 Datamapper (Item and Image) as well as 2 Models for them. I have an Item Controller, that one jumps into the ItemMapper. In the Datamapper I have a save Method. Please see below:
public function save(Application_Model_Item $item)
{
$data = array(
'item_title' => $item->getItemTitle(),
'item_description' => $item->getItemDescription(),
);
$this->getDbTable()->insert($data);
// Add Image Information ($imageData is Session Data)
$table = new Application_Model_DbTable_Image();
foreach ($imageData as $fileData)
{
$image = new Application_Model_Image($fileData);
$data = array(
'image_newname' => $image->getNewImageName(),
'image_thumbname' => $image->getImageThumbName(),
);
$table->insert($data);
}
Now the question I have.
I have Getter and Setter in my Model. Should everything what is in the Item Database Table be in 1 Model? Means everything about the Image should go to the Image Model since it is in a different Database Table?
Is it correct that I save the Information about the Image in the Item Mapper? Shoudl I not have a save() Method in the ImageMapper and save it in there? If so, how do I jump from the Item Mapper to the Image Mapper? Or would I first finish everything about the Item, return the ID to the Controller and than call the ImageMapper from the Controller?
I read something about "Fat Model Thin Controller". I had this all the time in my had, but I noticed that my Controller got pretty fat with just putting the Form together. I have about 5 Dropdown Fields which are depending Dropdowns. When I saw that I duplicating Code I decided to add this in a separate Function. So f.e. I have a Dropdown for counties. I wrote a Function which is also in my Controller so it looks like this:
public function getCounties ()
{
// List of Counties does not exist in Cache, read from DB
if(!$CountyList = $this->getFromCache('counties')){
$geolocationMapper = new Application_Model_GeolocationMapper();
$CountyDropdown = $geolocationMapper->createCountyDropdown();
// Save DB Result in Cache
$this->addToCache ('counties',$CountyDropdown);
return $CountyDropdown;
}
else{
// Return Country List from Cache
return $this->getFromCache('counties');
}
}
In my Add Function I use
// Assign Geo Info to Form
$CountyList = $this->getCounties();
$form->getElement('county')->setMultiOptions($CountyList);
The Edit Function than
$CountyList = $this->getCounties();
$form->getElement('county')->setMultiOptions($CountyList)->setValue($activeCounty)
all the Functions like getCounties () stay in the Controller or should it be moved to the GeolocationMapper? And if so, how would that be called up?
Should the Form be created in some Function so I would only call up something like createForm() ? I really have a lot of duplication (Add and Edit Function) and than Stuff comes from Database or Form was not Valid and it comes from Cache with a setValue. It just adds up when using dependable Dropdowns.
I know this are lots of questions, but I have the feeling it gets very messy, as a learner you are happy when it works, but I would also like to structure it in a proper way. I hope it all makes sense.
Maybe some of you have a few Tipps I could use. Thanks a lot for your help in advance.
There are quite a few questions here and most of the answers will be down largely to personal preference. With that caveat out of the way:
Should everything what is in the Item Database Table be in 1 Model?
I would say yes, although in general, try and think about it from the perspective of the models rather than the database structure. So all of the 'item' data goes in the Item model - the fact that this is all stored in one database table is irrelevant, since the mapper handles the translation from one to the other.
Is it correct that I save the Information about the Image in the Item Mapper?
It's not clear where $imageData comes from in your example, but I'd say the Item mapper should call the Image mapper if there is image data to save, e.g.:
public function save(Application_Model_Item $item)
{
$data = array(
'item_title' => $item->getItemTitle(),
'item_description' => $item->getItemDescription(),
);
$this->getDbTable()->insert($data);
// save image data if present
if (isset($item->image)) {
$imageMapper = new Yourapp_Mapper_Image();
$imageMapper->save($item->image);
}
return true;
}
[Should] all the Functions like getCounties () stay in the Controller or should it be moved to the GeolocationMapper? And if so, how would that be called up?
I don't see any reason for these functions to be in the controller. Depending on how comfortable you are with the Zend_Form component, one approach might be to write a custom Yourapp_Form_Element_Counties class that extends Zend_Form_Element_Select. You then move your logic from the getCounties function into the this class, so the form element itself is responsible for populating the options it presents. E.g.:
class Yourapp_Form_Element_Counties extends Zend_Form_Element_Select
{
public function getMultiOptions()
{
// load counties here and return an array in the format key -> value
}
}
Another approach, if you have a lot of location-related form elements, might be to create a GeoLocation Service class, which has a function for counties, cities etc. that returns the options.
Should the Form be created in some Function so I would only call up something like createForm()
It's not clear how much form stuff you are doing in the controller already apart from populating select options, so it's hard to answer this one. Here are the principles I generally follow when using Zend_Form:
Each form has its own class, extending Zend_Form (or Zend_Dojo_Form), which exists in application/forms and is named Myapp_Form_*
Each form class sets up its elements in the init() method (which is called automatically by Zend_Form's constructor for exactly this purpose).
If I find I need slight variations to the same form in different actions (e.g. in an add and edit action), I create an abstract base class for the form (e.g. Myapp_Form_Post_Base) which defines the elements, and then action-specific classes which extend it: Myapp_Form_Post_Add, Myapp_Form_Post_Edit and so on. These classes make any changes they need to the base form in their own init() method.
My actions then look something like this:
public function editAction()
{
$form = new Myapp_Form_Post_Edit();
if ($this->_request->isPost()) {
if ($form->isValid($this->_request->getPost()) {
// save data, set flash messge and redirect
}
}
$this->view->form = $form;
}
My only other piece of advice is to try and follow the approach which seems most logical to you. You might find it difficult to find definitive 'best practices' for a lot of this stuff since there are many different ways to do it all.
I have tried to follow your ideas as much as possible, so all the calls for the Counties, Towns and Postcodes are in the GeolocationMapper and the saving of the Image Data is in the ImageMapper.
You asked of how I create my Form right now, here an example from my init() of the Form for the Counties etc...
// Create Dropdown for Counties
$county = new Zend_Form_Element_Select('county');
$county->setLabel('Select a County')
->setRegisterInArrayValidator(false)
->setDecorators(array(
'ViewHelper',
'Errors',
array('Description',
array('tag' => 'p', 'class'=>'description')),
'Label',
array('HtmlTag',
array('tag'=>'li','class'=>'county'))
))
->setRequired(false);
// Create Dropdown for Town
$town = new Zend_Form_Element_Select('town');
$town->setRegisterInArrayValidator(false)
->setDecorators(array(
'ViewHelper',
'Errors',
array('Description', array('tag' => 'p', 'class' => 'description')),
'Label',
array('HtmlTag',array('tag'=>'li','class'=>'town'))
));
// Create Dropdown for Postcode
$postcode = new Zend_Form_Element_Select('postcode');
$postcode->setRegisterInArrayValidator(false)
->setDecorators(array(
'ViewHelper',
'Errors',
array('Description', array('tag' => 'p', 'class' => 'description')),
'Label',
array('HtmlTag',array('tag'=>'li','class'=>'postcode'))
))
->setRegisterInArrayValidator(false);
In my Controller I than get the Elements and fill them:
$geolocationMapper = new Application_Model_GeolocationMapper();
$CountyOptions = $geolocationMapper->createCountyDropdown();
$form->getElement('county')->setMultiOptions($CountyOptions);
In my GeolocationMapper I have the Methods to build my Array of Counties:
/** ===========================================================================
* Get Counties
* #param
* #return Object? of Counties
* ========================================================================= */
public function getCountyList()
{
$table = $this->getDbTable();
$select = $table->select()->distinct()
->from(array('p' => 'geolocation'),'county')
->order('county');
$resultSet = $this->getDbTable()->fetchAll($select);
$entries = array();
foreach ($resultSet as $row)
{
$entry = new Application_Model_Geolocation();
$entry->setId($row->county)
->setCounty($row->county);
$entries[] = $entry;
}
return $entries;
}
/** ===========================================================================
* Create Array which will be used for Dropdown
* #param
* #return Array of Counties
* ========================================================================= */
public function createCountyDropdown()
{
// List of Counties does not exist in Cache, read from DB
if(!$CountyList = $this->getFromCache('counties'))
{
$CountyList = $this->getCountyList();
$Counties[''] = "Please choose";
foreach($CountyList as $value)
{
$Counties[str_replace(' ','_',$value->getCounty())] = $value->getCounty();
}
$CountyDropdown = $Counties;
// Save DB Result in Cache
$this->addToCache ('counties',$CountyDropdown);
return $CountyDropdown;
}else{
return $this->getFromCache('counties');
}
}
The Counties I read in my GeolocationMapper. The Towns and Postcodes get read when you choose a County, which than calls via Ajax the Geolocation Mapper and than createTownDropdown($county) and when a Town is choosen the same procedure but an Ajax call for loadPostcodes() and there createPostcodeDropdown($town).
Does this all sounds correct or any suggestions how I could improve this?
I am sorry but I would really like to add another question since I can't find an answer anywhere... I also have an Image Upload which works via Ajax and jQuery. When you choose an Image to upload, the Image gets straight displayed. For this I create dynamically an input Element with an image src. I have not found ny other way to add Images otherwise to Zend Form. Is there a possibility to add an Image to display the image as a normal ? It would be just a lot easier to have a img, since I would like to use jQuery Drag and Drop. Thanks so much for your help so far !!!

Symfony: How to hide form fields from display and then set values for them in the action class

I am fairly new to symfony and I have 2 fields relating to my table "Pages"; created_by and updated_by. These are related to the users table (sfGuardUser) as foreign keys. I want these to be hidden from the edit/new forms so I have set up the generator.yml file to not display these fields:
form:
display:
General: [name, template_id]
Meta: [meta_title, meta_description, meta_keywords]
Now I need to set the fields on the save. I have been searching for how to do this all day and tried a hundred methods. The method I have got working is this, in the actions class:
protected function processForm(sfWebRequest $request, sfForm $form)
{
$form_params = $request->getParameter($form->getName());
$form_params['updated_by'] = $this->getUser()->getGuardUser()->getId();
if ($form->getObject()->isNew()) $form_params['created_by'] = $this->getUser()->getGuardUser()->getId();
$form->bind($form_params, $request->getFiles($form->getName()));
So this works. But I get the feeling that ideally I shouldnt be modifying the web request, but instead modifying the form/object directly. However I havent had any success with things like:
$form->getObject()->setUpdatedBy($this->getUser()->getGuardUser());
If anyone could offer any advice on the best ways about solving this type of problem I would be very grateful.
Thanks,
Tom
After processing and saving the form you could set those fields on the object and re-save:
protected function processForm(sfWebRequest $request, sfForm $form)
{
$form->bind($request->getParameter($form->getName()));
if ($form->isValid())
{
$page = $form->save();
$user = $this->getUser()->getGuardUser();
$page->setUpdatedBy($user);
if (empty($page->created_by))
{
$page->setCreatedBy($user);
}
$page->save();
$this->getUser()->setFlash('notice', 'Successfully saved page.');
$this->redirect('#homepage');
}
}
There's also a Doctrine extension called Blameable that automatically sets edited_by and created_by fields on specified models. The Doctrine website is undergoing some reorganization but here is the cached page for the extension.
To process your form create a new object, set the fields then save.
$article = new Article();
$article->setName($request->getParameter($form->getName());
$article->setDescription($request->getParameter($form->getDescription());
$article->setMetaKeywords($request->getParameter($form->getMetaKeywords());
$article->save();
What you want to do is customize your form and unset the 'created_at' and 'updated_at' pieces of the form in configure
class SampleForm extends BaseSampleForm
{
public function configure()
{
unset(
$this['created_at'],
$this['updated_at']
);
}
}
Then they won't show up in the form and will get the values setup by the "Timestampable" behavior before being saved
http://stereointeractive.com/blog/2010/04/07/symfony-forms-hide-created_at-updated_at-columns/