phpzend-frameworkzend-route

Zend Framework route: unknown number of params


I'm trying to write a route for an level N category depth. So an usual category URL would look like this:

http://website/my-category/my-subcategory/my-subcategory-level3/my-subcategory-level4

It has an unknown depth and my route has to match all possible levels. I made a route for this, but I can't get all the params from my controller.

$routeCategory = new Zend_Controller_Router_Route_Regex(
    '(([a-z0-9-]+)/?){1,}',
        array(
            'module' => 'default',
            'controller' => 'index',
            'action' => 'index'
        ),
        array( 1 => 'path'),
        '%s'
);
$router->addRoute('category', $routeCategory);

I can't seem to find a way to send the route matched params to the controller. If you have a better solution, I'm open to suggestions!


Solution

  • I found a solution that I think fits my needs. I'll post it here for people who will end up in the same thing I got into.

    Problem:

    Solution:

    Actual code:

    <?php
    
    class App_Controller_Router_Route_Category extends Zend_Controller_Router_Route_Regex
    {
        public function match($path, $partial = false)
        {
            if (!$partial) {
                $path = trim(urldecode($path), '/');
            }
    
            $values = explode('/', $path);
            $res = (count($values) > 0) ? 1 : 0;
            if ($res === 0) {
                return false;
            }
    
            /**
             * Check if first param is an actual module
             * If it's a module, let the default routing take place
             */
            $modules = array();
            $frontController = Zend_Controller_Front::getInstance();
            foreach ($frontController->getControllerDirectory() as $module => $path) {
                array_push($modules, $module);
            }
    
            if(in_array($values[0], $modules)) {
                return false;
            }
    
            if ($partial) {
                $this->setMatchedPath($values[0]);
            }
    
            $myValues = array();
            $myValues['cmsCategory'] = array();
    
            // array_filter_key()? Why isn't this in a standard PHP function set yet? :)
            foreach ($values as $i => $value) {
                if (!is_int($i)) {
                    unset($values[$i]);
                } else {
                    if(preg_match('/.html/', $value)) {
                        $myValues['cmsObject'] = $value;
                    } else {
                        array_push($myValues['cmsCategory'], $value);
                    }
                }
            }
    
            $values = $myValues;
            $this->_values = $values;
    
            $values   = $this->_getMappedValues($values);
            $defaults = $this->_getMappedValues($this->_defaults, false, true);
    
            $return   = $values + $defaults;
    
            return $return;
        }
    
        public function assemble($data = array(), $reset = false, $encode = false, $partial = false)
        {
            if ($this->_reverse === null) {
                require_once 'Zend/Controller/Router/Exception.php';
                throw new Zend_Controller_Router_Exception('Cannot assemble. Reversed route is not specified.');
            }
    
            $defaultValuesMapped  = $this->_getMappedValues($this->_defaults, true, false);
            $matchedValuesMapped  = $this->_getMappedValues($this->_values, true, false);
            $dataValuesMapped     = $this->_getMappedValues($data, true, false);
    
            // handle resets, if so requested (By null value) to do so
            if (($resetKeys = array_search(null, $dataValuesMapped, true)) !== false) {
                foreach ((array) $resetKeys as $resetKey) {
                    if (isset($matchedValuesMapped[$resetKey])) {
                        unset($matchedValuesMapped[$resetKey]);
                        unset($dataValuesMapped[$resetKey]);
                    }
                }
            }
    
            // merge all the data together, first defaults, then values matched, then supplied
            $mergedData = $defaultValuesMapped;
            $mergedData = $this->_arrayMergeNumericKeys($mergedData, $matchedValuesMapped);
            $mergedData = $this->_arrayMergeNumericKeys($mergedData, $dataValuesMapped);
    
            /**
             * Default Zend_Controller_Router_Route_Regex foreach insufficient
             * I need to urlencode values if I bump into an array
             */
            if ($encode) {
                foreach ($mergedData as $key => &$value) {
                    if(is_array($value)) {
                        foreach($value as $myKey => &$myValue) {
                            $myValue = urlencode($myValue);
                        }
                    } else {
                        $value = urlencode($value);
                    }
                }
            }
    
            ksort($mergedData);
    
            $reverse = array();
            for($i = 0; $i < count($mergedData['cmsCategory']); $i++) {
                array_push($reverse, "%s");
            }
            if(!empty($mergedData['cmsObject'])) {
                array_push($reverse, "%s");
                $mergedData['cmsCategory'][] = $mergedData['cmsObject'];
            }
    
            $reverse = implode("/", $reverse);
            $return = @vsprintf($reverse, $mergedData['cmsCategory']);
    
            if ($return === false) {
                require_once 'Zend/Controller/Router/Exception.php';
                throw new Zend_Controller_Router_Exception('Cannot assemble. Too few arguments?');
            }
    
            return $return;
    
        }
    }
    

    Usage:

    Route:

    $routeCategory = new App_Controller_Router_Route_Category(
            '',
            array(
                'module' => 'default',
                'controller' => 'index',
                'action' => 'index'
            ),
            array(),
            '%s'
    );
    $router->addRoute('category', $routeCategory);
    

    URL Helper:

    echo "<br>Url: " . $this->_helper->url->url(array(
                                'module' => 'default',
                                'controller' => 'index',
                                'action' => 'index',
                                'cmsCategory' => array(
                                    'first-category',
                                    'subcategory',
                                    'subsubcategory')
                                ), 'category');
    

    Sample output in controller with getAllParams()

    ["cmsCategory"]=>
      array(3) {
        [0]=>
        string(15) "first-category"
        [1]=>
        string(16) "subcategory"
        [2]=>
        string(17) "subsubcategory"
      }
      ["cmsObject"]=>
      string(15) "my-page.html"
      ["module"]=>
      string(7) "default"
      ["controller"]=>
      string(5) "index"
      ["action"]=>
      string(5) "index"