Я начал изучать Zend Framework и сразу же столкнулся с проблемой. Когда я разместил мой тестовый пример в подкаталог сайта (http://localhost/test/), роутер по-умолчанию начал искать TestController, который, естественно, не существует, и перенаправил меня на IndexController/noRoute. Это не очень хорошо. Я решил написать свой собственный Router.
В Zend Framework наблюдается некоторая непоследовательность (возможно потому, что это первая версия). Для того, чтобы создать свой собственный router, необходимо написать класс, реализующий интерфейс Zend_Controller_Router_Interface:
1 2 3 4 5 6 7 8 9 10 11 | 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 использует этот интерфейс следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Zend_Controller_Front { // ... /** * Dispatch an HTTP request to a controller/action. */ public function dispatch() { // ... $action = $this->getRouter()->route($this->getDispatcher()); // ... } } |
Забавная шутка от разработчиков Zend Framework. Front controller передает dispatcher как параметр router’у, но интерфейс последнего не принимает его. :-) Простейший способ, помогающий избежать сообщения об ошибке PHP,- определить параметр, принимающий значение по умолчанию null. Вот мой класс:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | /** 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; } } } |
Пример использования:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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(); |
Чтобы заставить это заработать, необходимо разместить SubDirectoryRouter в файле SubDirectoryRouter.php, и положить его в каталог подключения по умолчанию.

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).
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/
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.
As I understood from mailing list, default router allready patched and will be available in next release (0.1.3).
Dinh:
[...] http://kpumuk.info/php/zend-framework-router-for-subdirectory-based-site/ [...]
Приветствую, Дмитро!
Спасибо за весьма интересные исследования!
Меня, вот, интересует, как в ZF реализовать ЧПУ вроде: http://host/news/2006/01/01/this-is-a-cool-article-title/ ?
Заранее спасибо!
С уважением,
Михаил
Здравствуйте, Михаил
Во-первых, Вам нужно создать собственный роутер, поскольку стандартный предназначен для передачи параметров в виде:
http://host/controller/action/param1/value1/param2/value2/
Вы можете скопировать код стандартного роутера, и на его основе создать свой, изменив следующие строки:
Обработка запроса:
2
3
4
5
6
7
8
9
$controller = $path[0];
$action = isset($path[1]) ? $path[1] : null;
if (!strlen($controller)) {
$controller = 'index';
$action = 'index';
}
//...
на
2
3
4
$controller = $path[0];
$action = 'index';
//...
Обработка параметров:
2
3
4
5
6
$params = array();
for ($i=2; $i<sizeof($path); $i=$i+2) {
$params[$path[$i]] = isset($path[$i+1]) ? $path[$i+1] : null;
}
//...
на что-то вроде
2
3
4
5
6
$params = array();
for ($i=1; $i<sizeof($path); $i++) {
$params[] = $path[$i];
}
//...
Теперь осталось просто определить NewsController с методом index().
На следующей неделе я собираюсь написать пример использования Zend Framework, где будет использоваться подобная техника.
Спасибо, Дмитро!
Я просто сам только “въезжаю” во фремворки.. Пытался осилить Symfony — не получилось, CakePHP — попроще, тем не менее, все говорят, что время на их изучение для создания рабочего приложения (а не просто blabla.php) нужно отсчитывать в месяцах. Почитал обзор ZF и заинтересовался очень, поскольку спустя 2 часа могу что-то изобразить тогда как с Ruby-oriented type of frameworks приходится делать слишком много операций, что только осложняет работу. Хотя скорее всего все от того, что у меня просто нет много времени для их изучения. Порой даже кажется, что надо сперва изучить структуру ROR, чтоб потом что-то написать с помощью Symfony/Cake :)))))
Чего вот очень хотелось бы, тем не менее, в ZF, так это поддержку ORM, т.е. чего-то вроде Propel, но желательно еще проще. :))
Случайно совсем наткнулся на ваш сайт, так что теперича буду следить.. ;)
С уважением,
Михаил
К сожалению, никаких средств ORM в ZF не будет (по заявлениям разработчиков). Фреймворк преследует несколько целей, включая отсутствие необходимости в файлах конфигурации (configuration-less), простота, эффективность. Propel же не только требует настроить, что весьма и весьма нетривиально, но и вводит еще один этап – генерацию классов (хотя, надо признать, довольно удобно получается :-) ). В общем именно такая тема поднималась в списках рассылки, и разработчики сказали свое веское “нет”. Посмотрим, если будет желание, попробую их как-нибудь красивенько увязать :-) (но обещать ничего не буду за неимением достаточного количества свободного времени).
Приглашаю к русскоязычному обсуждению Zend Framework в этой конференции: http://groups.google.ru/group/ru-zend-framework
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:
2
3
4
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/
2
3
4
5
6
7
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 :
2
3
4
5
6
7
8
9
10
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 :
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']); ?>”>
[...] 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 [...]