Zend_Navigation: przyjazne linki

by Mateusz Tymek — on Zend Framework, PHP

Head's up! This post was written back in 2009 and is very likely to contain outdated information.

Wyobraźmy sobie następujący kontroler:

class UserController extends Zend_Controller_Action
{
    public function loginAction()
    {
        (...)
    }

    public function registerAction()
    {
        (...)
    }
}

Chcielibyśmy aby użytkownik miał dostęp do tych akcji po wpisaniu "przyjaznych" adresów: domena.com/logowanie i domena.com/rejestracja. Jak uzyskać takie nazwy? Można zmienić nazwę kontrolera i akcji, można też dodać trasy statyczne (Zend_Controller_Router_Route_Static). Są to mało elastyczne rozwiązania, które można zastosować jedynie w prostych aplikacjach. Lepsza metoda to odpowiednie wykorzystanie komponentu Zend_Navigation, dostępnego w ZF począwszy od wersji 1.8.

Na potrzeby przykładu wyobraźmy sobie listę odnośników w menu głównym witryny: strona główna, artykuły, o stronie, logowanie, rejestracja. Każdy z nich będzie prowadził do odpowiedniego kontrolera i odpowiedniej akcji. Utwórzmy zatem odpowiedni obiekt klasy Zend_Navigation:

$navigation = new Zend_Navigation(array(
   array(
       'label'      => 'Start',
       'controller' => 'index',
       'action'     => 'index',
       'link'       => ''
   ),
   array(
       'label'      => 'Artykuły',
       'controller' => 'articles',
       'action'     => 'index',
       'link'       => 'artykuly'
   ),
   array(
       'label'      => 'O Stronie',
       'controller' => 'index',
       'action'     => 'about',
       'link'       => 'o-stronie'
   ),
   array(
       'label'      => 'Logowanie',
       'controller' => 'user',
       'action'     => 'login',
       'link'       => 'logowanie'
   ),
   array(
       'label'      => 'Rejestracja',
       'controller' => 'user',
       'action'     => 'register',
       'link'       => 'rejestracja'
   )
));

Mamy 5 akcji w 3 kontrolerach. Do każdej strony dodałem pole link - definiujące "przyjazny" odnośnik.

Wyświetlenie menu to jedna linijka w pliku widoku:

$this->navigation($navigation)->menu();

Teraz najważniejsza część - sprawienie że wpisanie przyjaznego linku w przeglądarce odpowiednio przekieruje użytkownika. Wykorzystamy możliwości standardowego routera i utworzymy nową trasę, która znajdzie docelowy kontroler i akcję w drzewku nawigacji.

Trasa powinna być implementacją interfejsu Zend_Controller_Router_Route_Interface. Musi zawierać metodę match(), która sprawdzi czy dane żądanie pasuje do naszego schematu nawigacji. Drugą metodą którą zdefiniujemy jest assemble() - generująca link który zostanie potem wyświetlony w menu. W całości klasa będzie wyglądać tak:

class My_Controller_Router_Route_Sef extends Zend_Controller_Router_Route_Abstract
{
    /**
     * Action helper for assembling URLs
     *
     * @var Zend_Controller_Action_Helper_Url
     */
    protected static $_urlHelper = null;

    /**
     * @var Zend_Navigation
     */
    protected $_navigation = null;

    public function getVersion() {
        return 1;
    }

    public static function getInstance(Zend_Config $config)
    {
        return new self(null);
    }

    public function __construct($navigation)
    {
        $this->_navigation = $navigation;
    }

    public function match($path, $partial = false)
    {
        
        $path = trim($path, '/');

        if (empty($path)) {
            return array(
                'module' => 'default',
                'controller' => 'index',
                'action' => 'index'
            );
        }

        $item = $this->_navigation->findBy('link', $path);

        if (null === $item) {
            return false;
        }
        $a = array(
            'controller' => $item->getController(),
            'action' => $item->getAction()
        );
        return $a;        
    }

    public function assemble($data = array(), $reset = false, $encode = false, $partial = false)
    {
        $pages = $this->_navigation->findAllBy('controller', $data['controller']);

        $page = null;

        foreach ($pages as $p) {

            if ($p->action == $data['action']) {
                $page = $p;
                break;
            }

        }

        if (null !== $page && null !== $page->get('link')) {

            return $page->link;

        } else {

            if (null === self::$_urlHelper) {
                self::$_urlHelper =
                    Zend_Controller_Action_HelperBroker::getStaticHelper('Url');
            }

            $url = self::$_urlHelper->simple($data['action'],
                                             $data['controller'],
                                             'default'
                                            );            
            return trim($url, '/');
        }

    }
}

Zadaniem metody getInstance() jest utworzenie trasy na podstawie pliku konfiguracyjnego - zostawiam ją pustą aby nie komplikować przykładu. Natomiast getVersion() to taki kwiatek, przypadłość ZF pozostawiona chyba ze względu na wsteczną kompatybilność.

Teraz wystarczy poinformować router aby używał naszej nowo zdefiniowanej trasy:

$route = new Mango_Controller_Router_Route_Sef($navigation);
      
$router = $this->_frontController->getRouter();
$router->addRoute('navigation', $route);

Powyższy kod należy umieścić gdzieś w bootstrapie.

W efekcie menu wygenerowane w pliku widoku będzie składało się z przyjaznych linków, a nasza aplikacja będzie umiała je obsłużyć.


comments powered by Disqus