Some kind of "Subcontrollers" with Symfony (Update)

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:

  1. <code>region:
  2.   class: sfDoctrineRouteCollection
  3.   options:
  4.   model: Region
  5.   module: region
  6.   prefix_path: region
  7.   column: id
  8.   with_wildcard_routes: true
  9.  
  10. country:
  11.   class: sfDoctrineRouteCollection
  12.   options:
  13.   model: Country
  14.   module: country
  15.   prefix_path: country
  16.   column: id
  17.   with_wildcard_routes: true</code>

The URLs will look like "http://www.example.com/backend.php/country/index" and "http://www.example.com/backend.php/region/index". But what I want is something like "http://www.example.com/backend.php/country/COUNTRY_ID/region/index", so the Region view is always bound to an specific country.

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:

  1. region:
  2.   class: sfDoctrineRouteCollection
  3.   options:
  4.   model: Region
  5.   module: region
  6.   <b>prefix_path: country/:country_id/region</b>
  7.   column: id
  8.   with_wildcard_routes: true

If you try to open "http://www.example.com/backend.php/country/1/region/index" now (assuming that '1' is a valid country id) you'll get an error like this:

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.

apps/backend/modules/region/actions/actions.class.php:

  1. public function execute($sfRequest)
  2. {
  3. $this-&gt;forward404Unless($country_id = $sfRequest-&gt;getUrlParameter(‘country_id’));
  4. $this-&gt;forward404Unless($this-&gt;country = Doctrine::getTable(‘Country’)-&gt;find($country_id));
  5. $this-&gt;getContext()-&gt;getRouting()-&gt;setDefaultParameter(‘country_id’, $country_id);
  6. if ($id = $sfRequest-&gt;getUrlParameter(‘id’))
  7. {
  8. $this-&gt;getContext()-&gt;getRouting()-&gt;setDefaultParameter(‘id’, $id);
  9. }
  10. $result = parent::execute($sfRequest);
  11.  
  12. // UPDATE: This is required for the 'new' action
  13. if (isset($this-&gt;form) &amp;&amp; $this-&gt;form-&gt;getObject() &amp;&amp; $this-&gt;form-&gt;getObject()-&gt;isNew())
  14. {
  15. $this-&gt;form-&gt;getObject()-&gt;country_id = $country_id;
  16. }
  17. return $result;
  18. }

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()).

Comments

Thank you for the hint, I have the same need for my current project. But I am a bit beginner with symfony and I don't know where and how to override my index action in order to retrieve only regions concerned by the country_id parameter.
Can you help me?

Best regards,
Renaud

Thanks!
That's exactly what i needed!

Thanks for explanation why it doesn't work by default.

@Renaud
Put this code in file:
/Project/apps/yourApp/modules/yourModule/actions/action.class.php

Very happy I stumble upon your site!! This is great!

For the new form part I had to do it differently since the action would get executed before reaching that code.

public function postExecute()
{
if (isset($this->form) && $this->form->getObject() && $this->form->getObject()->isNew())
{
$this->form->getObject()->country_id = $this->getRequest()->getUrlParameter('country_id');
}
}