This is a post from my original site, which was hosted by the former blog service of the University of Osnabrück. I have moved it to the new site for archiving. Pages linked in this article may no longer work today, and the blog comments under the article no longer exist. Opinions expressed in this article reflect the point of view of the time of publication and do not necessarily reflect my opinion today.
If you're running a Drupal-based webpage which is completely restricted to authenticated users, Anonymous would get an "403 Access Denied" on every page he tries. But if you want your visitors to see a friendly login page (instead of just this error message and a small "login block") you might want to put the following code into your sites/default/setting.php:
You should also check $path if you want to use this on some pages only, e.g. on your frontpage.
UPDATE: This code is erroneous! You should return $result instead of $path, or some other modules like pathauto or path_redirect won't work as expected!
This is a post from my original site, which was hosted by the former blog service of the University of Osnabrück. I have moved it to the new site for archiving. Pages linked in this article may no longer work today, and the blog comments under the article no longer exist. Opinions expressed in this article reflect the point of view of the time of publication and do not necessarily reflect my opinion today.
Symfony provides a nice feature called "embedded Forms" ( sfForm::embedForm) to embed subforms into a parent form. This can be used to edit multiple records at the same time. So let's say you have a basic user table called 'sf_guard_user' and a profile table called 'user_profile', then you might follow this guide to merge these forms together: lib/forms/doctrine/sfUserGuardAdminForm.php:
classsfGuardUserAdminFormextendsBasesfGuardUserAdminForm{publicfunctionconfigure(){parent::configure();// Embed UserProfileForm into sfGuardUserAdminForm$profileForm=newUserProfileForm($this->object->Profile);unset($profileForm['id'],$profileForm['sf_guard_user_id']);$this->embedForm("profile",$profileForm);}}
Remember to add "profile" to the list of visible columns in apps/backend/modules/sfGuardUser/config/generator.yml as decribed in the linked guide. The result may look like this:
This does what it is expected to do, but it doesn't look very nice. Especially for 1:1 related tables I'm more interested in a solution that looks like this:
You can reach this using sfForm::mergeForm, but sadly the merged model won't get updated and you'll run into problems if the forms are sharing fieldnames. The solution is the following method embedMergeForm which can be defined in BaseFormDoctrine to be avaible in all other forms:
lib/forms/doctrine/BaseFormDoctrine.php:
abstractclassBaseFormDoctrineextendssfFormDoctrine{/**
* Embeds a form like "mergeForm" does, but will still
* save the input data.
*/publicfunctionembedMergeForm($name,sfForm$form){// This starts like sfForm::embedForm$name=(string)$name;if(true===$this->isBound()||true===$form->isBound()){thrownewLogicException('A bound form cannot be merged');}$this->embeddedForms[$name]=$form;$form=clone$form;unset($form[self::$CSRFFieldName]);// But now, copy each widget instead of the while form into the current// form. Each widget ist named "formname|fieldname".foreach($form->getWidgetSchema()->getFields()as$field=>$widget){$widgetName="$name|$field";if(isset($this->widgetSchema[$widgetName])){thrownewLogicException("The forms cannot be merged. A field name '$widgetName' already exists.");}$this->widgetSchema[$widgetName]=$widget;// Copy widget$this->validatorSchema[$widgetName]=$form->validatorSchema[$field];// Copy schema$this->setDefault($widgetName,$form->getDefault($field));// Copy default valueif(!$widget->getLabel()){// Re-create label if not set (otherwise it would be named 'ucfirst($widgetName)')$label=$form->getWidgetSchema()->getFormFormatter()->generateLabelName($field);$this->getWidgetSchema()->setLabel($widgetName,$label);}}// And this is like in sfForm::embedForm$this->resetFormFields();}/**
* Override sfFormDoctrine to prepare the
* values: FORMNAME|FIELDNAME has to be transformed
* to FORMNAME[FIELDNAME]
*/publicfunctionupdateObject($values=null){if(is_null($values)){$values=$this->values;foreach($this->embeddedFormsAS$name=>$form){foreach($formAS$field=>$f){if(isset($values["$name|$field"])){// Re-rename the form field and remove// the original field$values[$name][$field]=$values["$name|$field"];unset($values["$name|$field"]);}}}}// Give the request to the original methodparent::updateObject($values);}}
This method ensures that each fieldname is unique (named 'FORMNAME|FIELDNAME') and the subform is validated and saved. It is used like embedForm:
lib/forms/doctrine/sfUserGuardAdminForm.php:
classsfGuardUserAdminFormextendsBasesfGuardUserAdminForm{publicfunctionconfigure(){parent::configure();// Embed UserProfileForm into sfGuardUserAdminForm// without looking like an embedded form$profileForm=newUserProfileForm($this->object->Profile);unset($profileForm['id'],$profileForm['sf_guard_user_id']);$this->embedMergeForm("profile",$profileForm);}}
Feel free to use this method in your own project. Maybe this method get's merged into Symfony some day ;-)
Update
frostpearl reported a problem using embedFormMerge() in conjunction with the autocompleter widget from sfFormExtraPlugin. If you expire these problems try to replace all occurences of $name|$field with $name-$field.
This is a post from my original site, which was hosted by the former blog service of the University of Osnabrück. I have moved it to the new site for archiving. Pages linked in this article may no longer work today, and the blog comments under the article no longer exist. Opinions expressed in this article reflect the point of view of the time of publication and do not necessarily reflect my opinion today.
In my current Symfony project I have a model called "Country" and a model called "Region". A Region always belongs to a Country, and this country will never change.
I've used Doctrine's admin generator to create the administration backend:
$ php symfony doctrine:generate-admin --plural="Countries" backend Country
$ php symfony doctrine:generate-admin backend Region
This command produces two modules, "apps/backend/modules/country/" and "apps/backend/modules/region/", and the following entries in apps/backend/config/routing.yml:
region:class: sfDoctrineRouteCollection
options:model: Region
module: region
prefix_path: region
column: id
with_wildcard_routes:truecountry:class: sfDoctrineRouteCollection
options:model: Country
module: country
prefix_path: country
column: id
with_wildcard_routes:true
You can reach this with minimal effort. First, you have to modify the routing. Change the entry for region as shown below (changed line is in bold font):
apps/backend/config/routing.yml:
region:class: sfDoctrineRouteCollection
options:model: Region
module: region
prefix_path: country/:country_id/region
column: id
with_wildcard_routes:true
500 | Internal Server Error | InvalidArgumentException
The "/country/:country_id/region/:action/action.:sf_format" route has some missing mandatory parameters (:country_id).This is because the (automatically generated) region view tries to call 'url_for()' for actions like 'filter' or 'add', and there is an parameter called 'country_id' defined which is missing in the argument list. You can solve this problem by overwriting the 'execute()' function in the action class.
publicfunctionexecute($sfRequest){$this->forward404Unless($country_id=$sfRequest->getUrlParameter(‘country_id’));$this->forward404Unless($this->country=Doctrine::getTable(‘Country’)->find($country_id));$this->getContext()->getRouting()->setDefaultParameter(‘country_id’,$country_id);if($id=$sfRequest->getUrlParameter(‘id’)){$this->getContext()->getRouting()->setDefaultParameter(‘id’,$id);}$result=parent::execute($sfRequest);// UPDATE: This is required for the 'new' actionif(isset($this->form)&&$this->form->getObject()&&$this->form->getObject()->isNew()){$this->form->getObject()->country_id=$country_id;}return$result;}
This will set the current country id as default parameter for all calls to methods like 'link_to()' or 'url_for()', and abort if an valid id is missing.
Of course you still have to modify the filters and forms to regard the given country as default, and add some extra actions to the Country view, but the most trickiest part is done. Have a look at this article from Sven to learn how to modify the default filter and read Jobeet Tutorial, Chapter 12 to see how the default actions can be customized.
If you expire some problems with 'new' and 'edit' forms (action="/backend.php/country/region" in <form>-tag), read bug report #6881. Update: This problem can be solved by setting the 'country_id' field of new objects (e.g. by overriding executeNew() and executeCreate()).