views:

57

answers:

3

Hi

I'm using PHP 5.3's class_alias to help process my Symfony 1.4 (Doctrine) forms. I use a single action to process multiple form pages but using a switch statement to choose a Form Class to use.

public function executeEdit(sfWebRequest $request) {
  switch($request->getParameter('page')) {
    case 'page-1':
      class_alias('MyFormPage1Form', 'FormAlias');
    break;
    ...
  }
  $this->form = new FormAlias($obj);
}

This works brilliantly when browsing the website, but fails my functional tests, because when a page is loaded more than once, like so:

$browser->info('1 - Edit Form Page 1')->

  get('/myforms/edit')->
  with('response')->begin()->
    isStatusCode(200)->
  end()->

  get('/myforms/edit')->
  with('response')->begin()->
    isStatusCode(200)->
  end();

I get a 500 response to the second request, with the following error:

last request threw an uncaught exception RuntimeException: PHP sent a warning error at /.../apps/frontend/modules/.../actions/actions.class.php line 225 (Cannot redeclare class FormAlias)

This makes it very hard to test form submissions (which typically post back to themselves).

Presumably this is because Symfony's tester hasn't cleared the throughput in the same way. Is there a way to 'unalias' or otherwise allow this sort of redeclaration?

A: 
function class_alias_once($class, $alias) {
    if (!class_exists($alias)) {
        class_alias($class, $alias);
    }
}

This doesn't solve the problem itself, but by using this function it is ensured that you don't get the error. Maybe this will suffice for your purpose.

nikic
Jep, you're perfectly right. Neither parsekit nor classkit have such a method. Removed that part from my answer.
nikic
A: 

I do not know for sure if it is possible, but judging from the Manual, I'd say no. Once the class is aliased, there is no way to reset it or redeclare it with a different name. But then again, why do use the alias at all?

From your code I assume you are doing the aliasing in each additional case block. But if so, you can just as well simply instantiate the form in those blocks, e.g.

public function executeEdit(sfWebRequest $request) {
  switch($request->getParameter('page')) {
    case 'page-1':
      $form = new MyFormPage1Form($obj);
    break;
    ...
  }
  $this->form = $form;
}

You are hardcoding the class names into the switch/case block anyway when using class_alias. There is no advantage in using it. If you wanted to do it dynamically, you could create an array mapping from 'page' to 'className' and then simply lookup the appropriate class.

public function executeEdit(sfWebRequest $request) {
  $mapping = array(
      'page-1' => 'MyFormPage1Form',
      // more mappings
  );
  $form = NULL;
  $id = $request->getParameter('page');
  if(array_key_exists($id, $mapping)) {
       $className = $mapping[$id];
       $form = new $className($obj);
  }
  $this->form = $form;
}

This way, you could also put the entire mapping in a config file. Or you could create FormFactory.

public function executeEdit(sfWebRequest $request) {
    $this->form = FormFactory::create($request->getParameter('page'), $obj);
}

If you are using the Symfony Components DI Container, you could also get rid of the hard coded factory dependency and just use the service container to get the form. That would be the cleanest approach IMO. Basically, using class_alias just feels inappropriate here to me.

Gordon
There's more complexity to the function that I didn't show in the example which makes aliasing more useful than it seemed.But, sure, the mapping looks a better way to handle it.
kenneth
+1  A: 

As an alternate solution you can assign the name of the class to instantiate to a variable and new that:

public function executeEdit(sfWebRequest $request) {
  $formType;
  switch($request->getParameter('page')) {
    case 'page-1':
      $formType = 'MyFormPage1Form';
    break;
    ...
  }
  $this->form = new $formType();
}

This doesn't use class_alias but keeps the instantiation in a single spot.

Kwebble
Neat. Didn't know that was possible. Seems to make class_alias somewhat redundant. I assume there's a performance difference.
kenneth
class_alias has to keep a record of aliased classes, this introduces a local variable. Don't know which has more impact.class_alias must have been added to 5.3.0 for a reason, but I can't think of one.
Kwebble