ZF-11805: Zend_Navigation_Page error messages don't contain enough information

Description

Using a navigation.ini file, it is really easy to typo or make a mistake.

In Zend_Navigation_Page the exception 'Invalid argument: Unable to determine class to instantiate' on line 235 is really hard to fix. It should show the line that caused the error and why (e.g. no parent page, no container named 'blah' or something)

Comments

{quote}…why (e.g. no parent page, no container named 'blah' or something){quote} This has nothing to do with parent page or container.


throw new Zend_Navigation_Exception(
   'Invalid argument: Unable to determine class to instantiate '
   . '(URI page takes a option uri; MVC pages are defined using '
   . 'MVC parameters like action, controller, module, route or params)'
);

Another idea:


require_once 'Zend/Navigation/Exception.php';

$message = 'Invalid argument: Unable to determine class to instantiate';
if (isset($options['label'])) {
    $message .= ' (Label: ' . $options['label'] . ')';
}            

throw new Zend_Navigation_Exception($message);

Fatal error:
Uncaught exception 'Zend_Navigation_Exception' with message 'Invalid argument: Unable to determine class to instantiate (Label: Home)' in …

I find moving or renaming pages and missing one of the children (my navigation.ini file is 800+ lines long) is the most common error, e.g:

sales.pages.discounts.pages.category.pages.cms.pages.intro.label = Edit

then somewhere 'discounts' gets renamed to 'offers' then the above line throws:

Invalid argument: Unable to determine class to instantiate

because 'discounts' no longer exists... your solution would show the word 'Edit' which would be useful. Could we show the .ini file line number? Or anything more to help hunt down the offending change?

OK I just re-read my comment and it makes no sense - lol, say I have this:

sales.pages.discounts.label = Offers sales.pages.discounts.module = admin sales.pages.discounts.controller = offer sales.pages.discounts.action = index

... 100s of lines of ini file ...

sales.pages.discounts.pages.category.pages.cms.pages.intro.label = Edit sales.pages.discounts.pages.category.pages.cms.pages.intro.module = admin sales.pages.discounts.pages.category.pages.cms.pages.intro.controller = cms sales.pages.discounts.pages.category.pages.cms.pages.intro.action = editor

Then someone renames:

sales.pages.offers.label = Offers sales.pages.offers.module = admin sales.pages.offers.controller = offer sales.pages.offers.action = index

... then the following lines now throw errors:

sales.pages.discounts.pages.category.pages.cms.pages.intro.label = Edit sales.pages.discounts.pages.category.pages.cms.pages.intro.module = admin sales.pages.discounts.pages.category.pages.cms.pages.intro.controller = cms sales.pages.discounts.pages.category.pages.cms.pages.intro.action = editor

The app is run and we get that cryptic error message:

'Invalid argument: Unable to determine class to instantiate'

and the first place we start looking is in the lines of code we changed (which are correct and have no errors)

I assume that yaml navigation files don't have this problem?

Hi monk e boy, thanks for your feedback, but unfortunately there is no way to get the line number from the config file. All Zend_Config objects are converted to arrays and so there is no relation to the config file.


public function addPages($pages)
{
    if ($pages instanceof Zend_Config) {
        $pages = $pages->toArray();
    }

    // …

    foreach ($pages as $page) {
        $this->addPage($page);
    }

    // …
}

public static function factory($options)
{
    if ($options instanceof Zend_Config) {
        $options = $options->toArray();
    }

    // …

    $hasUri = isset($options['uri']);
    $hasMvc = isset($options['action']) || isset($options['controller']) ||
              isset($options['module']) || isset($options['route']);

    if ($hasMvc) {
        require_once 'Zend/Navigation/Page/Mvc.php';
        return new Zend_Navigation_Page_Mvc($options);
    } elseif ($hasUri) {
        require_once 'Zend/Navigation/Page/Uri.php';
        return new Zend_Navigation_Page_Uri($options);
    } else {
        require_once 'Zend/Navigation/Exception.php';
        
        $message = 'Invalid argument: Unable to determine class to instantiate';
        if (isset($options['label'])) {
            $message .= ' (Label: ' . $options['label'] . ')';
        }            
        
        throw new Zend_Navigation_Exception($message);
    }
}

{quote}I assume that yaml navigation files don't have this problem?{quote} This is only the syntax, because nothing needs to be written multiple times.

Example:


sales.pages.discounts.pages.category.pages.cms.pages.intro.label = Edit
sales.pages.discounts.pages.category.pages.cms.pages.intro.module = admin
sales.pages.discounts.pages.category.pages.cms.pages.intro.controller = cms
sales.pages.discounts.pages.category.pages.cms.pages.intro.action = editor

sales:
    pages:
        discounts:
            pages:
                category:
                    pages:
                        cms:
                            pages:
                                intro:
                                    label: Edit
                                    module: admin
                                    controller: cms
                                    action: editor

Would be brilliant if your last comment was in the documentation :) the yaml file looks a lot nicer for our front end devs to use.

Thanks for all your help :)

Tasks for this issue:

  • Extend the exception message ({{Zend_Navigation_Page::factory()}})
  • Add examples for INI, JSON and YAML to docs
  • Update xml example in the docs (syntax highlighting)

Fix and unit test added.

Patch for docs.

Fixed in trunk (1.12.0): r24854