Zend Framework: Router for subdirectory-based site

Mar 08
2006 12:36 (Development, PHP) · Русский (22,395 views)

I started discovering of Zend Framework and was confronted with a problem. When I’ve placed my test sample into site’s subdirectory (http://localhost/test/), default router tried to find TestController which is not exists of course and routed me to IndexController/noRoute. It’s not good for me. I decided to create my own router.

There some inconsistence in Zend Framework (maybe because this is first release). To create my own router I need to develop class which implements Zend_Controller_Router_Interface interface:

interface Zend_Controller_Router_Interface
{
    /**
     * Processes an HTTP request and routes to a Zend_Controller_Dispatcher_Action object.  If
     * no route was possible, an exception is thrown.
     *
     * @throws Zend_Controller_Router_Exception
     * @return Zend_Controller_Dispatcher_Action|boolean
     */

    public function route();
}

Zend_Controller_Front uses this interface in following way:

class Zend_Controller_Front
{
  // ...
  /**
   * Dispatch an HTTP request to a controller/action.
   */

  public function dispatch()
  {
    // ...
    $action = $this->getRouter()->route($this->getDispatcher());
    // ...
  }
}

Fine joke from Zend Framework’s developers. Front controller passes dispatcher to router, but router interface does not receive it. :-) Simplest way to avoid PHP error message is to define parameter which has default value null. Here my router class:

/** Zend_Controller_Router_Interface */
require_once 'Zend/Controller/Router/Interface.php';

/** Zend_Controller_Dispatcher_Interface */
require_once 'Zend/Controller/Dispatcher/Interface.php';

/** Zend_Controller_Router_Exception */
require_once 'Zend/Controller/Router/Exception.php';

/** Zend_Controller_Dispatcher_Action */
require_once 'Zend/Controller/Dispatcher/Action.php';

class SubDirectoryRouter implements Zend_Controller_Router_Interface
{
  public function route(Zend_Controller_Dispatcher_Interface $dispatcher = null)
  {
    // SubDirectoryRouter: what's the path to where we are?
    $pathIndex = dirname($_SERVER['SCRIPT_NAME']);

    // SubDirectoryRouter: remove $pathIndex from $_SERVER['REQUEST_URI']
    $path = str_replace($pathIndex, '', $_SERVER['REQUEST_URI']);
    if (strstr($path, '?')) {
      $path = substr($path, 0, strpos($path, '?'));
    }
    $path = explode('/', trim($path, '/'));

    /**
     * The controller is always the first piece of the URI, and
     * the action is always the second:
     *
     * http://zend.com/controller-name/action-name/
     */

    $controller = $path[0];
    $action     = isset($path[1]) ? $path[1] : null;

    /**
     * If no controller has been set, IndexController::index()
     * will be used.
     */

    if (!strlen($controller)) {
      $controller = 'index';
      $action = 'index';
    }

    /**
     * Any optional parameters after the action are stored in
     * an array of key/value pairs:
     *
     * http://www.zend.com/controller-name/action-name/param-1/3/param-2/7
     *
     * $params = array(2) {
     *              ["param-1"]=> string(1) "3"
     *              ["param-2"]=> string(1) "7"
     * }
     */

    $params = array();
    for ($i=2; $i<sizeof($path); $i=$i+2) {
      $params[$path[$i]] = isset($path[$i+1]) ? $path[$i+1] : null;
    }

    $actionObj = new Zend_Controller_Dispatcher_Action($controller, $action, $params);

    if (!$dispatcher->isDispatchable($actionObj)) {
      /**
       * @todo error handling for 404's
       */

      throw new Zend_Controller_Router_Exception('Request could not be mapped to a route.');
    } else {
      return $actionObj;
    }
  }
}

Sample usage:

require('Zend.php');

Zend::loadClass('Zend_Controller_Action');
Zend::loadClass('Zend_Controller_Front');
Zend::loadClass('Zend_Filter');
Zend::loadClass('Zend_InputFilter');
Zend::loadClass('Zend_View');

Zend::loadClass('SubDirectoryRouter');

$controller = Zend_Controller_Front::getInstance();
$controller->setControllerDirectory('controllers');

$router = new SubDirectoryRouter();
$controller->setRouter($router);

$controller->dispatch();

To make it work you need to place SubDirectoryRouter to the SubDirectoryRouter.php file and put it under include path.

12 Responses to 'Zend Framework: Router for subdirectory-based site'

Subscribe to comments with RSS or TrackBack to 'Zend Framework: Router for subdirectory-based site'.

1
said on 2006-03-08 at 5.24 pm

You will be pleased to see that the interface has been fixed in Preview Release 0.1.2, which is now available for download from the Zend Framework website (http://framework.zend.com).

2
said on 2006-03-08 at 7.10 pm

It’s not just me!!

I came to nearly exactly the same code when I started playing. Details at http://www.akrabat.com/2006/03/04/zend-framework-front-controller/

3
said on 2006-03-09 at 10.26 am

Thanks, Mike. I will look at new version. I think it’s too early to use it in real projects, but idea is great. This framework can become one of the most popular thing in PHP world.

Rob, no comments :-) My code looks too similar with your one. Maybe it’s really great problem for people who started working with this framework.

4
said on 2006-03-09 at 5.42 pm

As I understood from mailing list, default router allready patched and will be available in next release (0.1.3).

Dinh:

I patched Router.php in ZF with your code:

/**
 * @todo Replace with Zend_Request object
 */

//$path = $_SERVER['REQUEST_URI'];

// add

// what's the path to where we are?
$appRoutePath = dirname($_SERVER['SCRIPT_NAME']);       
$path = str_replace($appRoutePath, '', $_SERVER['REQUEST_URI']);

It works like a charm.

6
Михаил
said on 2006-03-17 at 1.30 pm

Приветствую, Дмитро!

Спасибо за весьма интересные исследования!
Меня, вот, интересует, как в ZF реализовать ЧПУ вроде: http://host/news/2006/01/01/this-is-a-cool-article-title/ ?

Заранее спасибо!
С уважением,
Михаил

7
said on 2006-03-17 at 3.11 pm

Здравствуйте, Михаил

Во-первых, Вам нужно создать собственный роутер, поскольку стандартный предназначен для передачи параметров в виде:
http://host/controller/action/param1/value1/param2/value2/

Вы можете скопировать код стандартного роутера, и на его основе создать свой, изменив следующие строки:

Обработка запроса:

//...
    $controller = $path[0];
    $action     = isset($path[1]) ? $path[1] : null;
 
    if (!strlen($controller)) {
      $controller = 'index';
      $action = 'index';
    }
//...

на

//...
    $controller = $path[0];
    $action = 'index';
//...

Обработка параметров:

//...
    $params = array();
    for ($i=2; $i<sizeof($path); $i=$i+2) {
      $params[$path[$i]] = isset($path[$i+1]) ? $path[$i+1] : null;
    }
//...

на что-то вроде

//...
    $params = array();
    for ($i=1; $i<sizeof($path); $i++) {
      $params[] = $path[$i];
    }
//...

Теперь осталось просто определить NewsController с методом index().

На следующей неделе я собираюсь написать пример использования Zend Framework, где будет использоваться подобная техника.

8
Михаил
said on 2006-03-17 at 4.05 pm

Спасибо, Дмитро!
Я просто сам только “въезжаю” во фремворки.. Пытался осилить Symfony — не получилось, CakePHP — попроще, тем не менее, все говорят, что время на их изучение для создания рабочего приложения (а не просто blabla.php) нужно отсчитывать в месяцах. Почитал обзор ZF и заинтересовался очень, поскольку спустя 2 часа могу что-то изобразить тогда как с Ruby-oriented type of frameworks приходится делать слишком много операций, что только осложняет работу. Хотя скорее всего все от того, что у меня просто нет много времени для их изучения. Порой даже кажется, что надо сперва изучить структуру ROR, чтоб потом что-то написать с помощью Symfony/Cake :)))))
Чего вот очень хотелось бы, тем не менее, в ZF, так это поддержку ORM, т.е. чего-то вроде Propel, но желательно еще проще. :))

Случайно совсем наткнулся на ваш сайт, так что теперича буду следить.. ;)

С уважением,
Михаил

9
said on 2006-03-17 at 4.21 pm

К сожалению, никаких средств ORM в ZF не будет (по заявлениям разработчиков). Фреймворк преследует несколько целей, включая отсутствие необходимости в файлах конфигурации (configuration-less), простота, эффективность. Propel же не только требует настроить, что весьма и весьма нетривиально, но и вводит еще один этап - генерацию классов (хотя, надо признать, довольно удобно получается :-) ). В общем именно такая тема поднималась в списках рассылки, и разработчики сказали свое веское “нет”. Посмотрим, если будет желание, попробую их как-нибудь красивенько увязать :-) (но обещать ничего не буду за неимением достаточного количества свободного времени).

10
said on 2006-04-05 at 11.56 am

Приглашаю к русскоязычному обсуждению Zend Framework в этой конференции: http://groups.google.ru/group/ru-zend-framework

11
Kevin Campion
said on 2006-04-19 at 1.27 am

I have just tested the Zend Framework, and I was confronted with the same problem (subdomain).
I found your explanation after some research. But with version 0.1.2 of Zend Framework, it doesn’t work.

There are the changes which I carried out for work :

At the beginning I used this tutorial to have a little application for testing : http://phparch.com/zftut/index.php?p=0

My structure look like this:

DOCUMENT ROOT > /www My First Application > /www/project1 Zend Framework Library > /www/zend/ZendFramework-0.1.2/library (php.ini contains include_path = ".:/www/zend/ZendFramework-0.1.2/library")

I wished to check my application by http://example.org/project1/

/www/project1/.htaccess : RewriteEngine on RewriteRule !\.(js|ico|gif|jpg|png|css)$ index.php /www/project1/app/controllers/IndexController.php /www/project1/app/views/index.php

/www/project1/index.php :

setScriptPath('./app/views');
Zend::register('view', $view);

$controller = Zend_Controller_Front::getInstance();
$controller->setControllerDirectory('./app/controllers');
 
$router = new SubDirectoryRouter();
$controller->setRouter($router);
 
$controller->dispatch();

When i check “http://localhost/project1/” I’ve got this error :

Fatal error: Class 'Zend_Controller_Dispatcher_Action' not found in /www/zend/ZendFramework-0.1.2/library/SubDirectoryRouter.php on line 73

It is cause of: $actionObj = new Zend_Controller_Dispatcher_Action($controller, $action, $params);
Replace it by $actionObj = new Zend_Controller_Dispatcher_Token($controller, $action, $params);
and in top of the file SubDirectoryRouter.php move require_once ‘Zend/Controller/Dispatcher/Interface.php’;
to require_once ‘Zend/Controller/Dispatcher/Token.php’;

In Zend Framework 0.1.2 it’s the right way (cf /www/zend/ZendFramework-0.1.2/library/Zend/Controller/Router.php)

When i recheck “http://localhost/project1/” it work.

D’ont forget to change in all views :
by
or
escape($entry['id']); ?>”> by escape($entry['id']); ?>”>

12
said on 2007-10-10 at 7.08 pm

[...] Zend Framework: Router for subdirectory-based site || Dmytro Shteflyuk’s Home - I’ve had problems with Zend Framework: Router for subdirectory-based site || Dmytro Shteflyuk’s Home [...]

Post a comment

You can use simple HTML-formatting tags (like <a>, <ul> and others). To format your code sample use <code lang="php">$a = "hello";</code> (allowed languages are ruby, php, yaml, html, csharp, javascript). Also you could use <code>$a = "hello";</code> and its syntax would not be highlighted. If you are not using <code> tag, replace < sign with &lt;.

Submit Comment

 
Copyright © 2005 - 2008, Dmytro Shteflyuk