I'm working on a Zend Framework 2 application for work in which I can't seem to route correctly or know where to route it.
I have an Hostname => webapp.foo-bar.com. We decided to add a Subhost => /app/ to the end and the name of this app is called => app. I have a link on a page which it's route would be say => /graph/page-name. But when I hover over the link which looks like:
FooBar
I'd get webapp.foo-bar.com/graph/page-name as opposed to webapp.foo-bar.com/app/graph/page-name.
My Application config is:
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* #link http://github.com/zendframework/ZendSkeletonApplication for the canonical source repository
* #copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
* #license http://framework.zend.com/license/new-bsd New BSD License
*/
return array(
'router' => array(
'routes' => array(
'home' => array(
'type' => 'hostname',
'options' => array(
'route' => 'webapp.foo-bar.com/app',
'defaults' => array(
'__NAMESPACE__' => 'Application\Controller',
'controller' => 'Index',
'action' => 'index',
),
),
),
// The following is a route to simplify getting started creating
// new controllers and actions without needing to create a new
// module. Simply drop new controllers in, and you can access them
// using the path /application/:controller/:action
'application' => array(
'type' => 'Literal',
'options' => array(
'route' => '/application',
'defaults' => array(
'__NAMESPACE__' => 'Application\Controller',
'controller' => 'Index',
'action' => 'index',
),
),
'may_terminate' => true,
'child_routes' => array(
'default' => array(
'type' => 'Segment',
'options' => array(
'route' => '/[:controller[/:action]]',
'constraints' => array(
'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
),
'defaults' => array(
),
),
),
),
),
),
),
'service_manager' => array(
'abstract_factories' => array(
'Zend\Cache\Service\StorageCacheAbstractServiceFactory',
'Zend\Log\LoggerAbstractServiceFactory',
),
'aliases' => array(
'translator' => 'MvcTranslator',
),
),
'translator' => array(
'locale' => 'en_US',
'translation_file_patterns' => array(
array(
'type' => 'gettext',
'base_dir' => __DIR__ . '/../language',
'pattern' => '%s.mo',
),
),
),
'controllers' => array(
'invokables' => array(
'Application\Controller\Index' => 'Application\Controller\IndexController'
),
),
'view_manager' => array(
'display_not_found_reason' => true,
'display_exceptions' => true,
'doctype' => 'HTML5',
'not_found_template' => 'error/404',
'exception_template' => 'error/index',
'template_map' => array(
'layout/layout' => __DIR__ . '/../view/layout/layout.phtml',
'application/index/index' => __DIR__ . '/../view/application/index/index.phtml',
'error/404' => __DIR__ . '/../view/error/404.phtml',
'error/index' => __DIR__ . '/../view/error/index.phtml',
),
'template_path_stack' => array(
__DIR__ . '/../view',
),
),
// Placeholder for console routes
'console' => array(
'router' => array(
'routes' => array(
),
),
),
);
I saw there is an option like:
FooBar
Would this need to be done on every link, or would we be able to do it in the config level?
Thanks!
Update:
I've managed to get the client to go down using the this->url() method and things are looking fine. Links are working even in the jQuery sections. I am having a small issue in relation to a link going to another controller not showing the action in the link.
<a href="<?php echo $this->url('foo-bar', array('action' => 'bar-foo'))?>?year=2015"</a>
Brings back webapp.foo-bar.com/app/foo-bar?year=2015. I want it to return webapp.foo-bar.com/app/foo-bar/bar-foo?year=2015. Is this a configuration in the Controller of foo-bar or can it be done in this-url()?
Thanks a million for all the help given. Really appriciate it!
If both apps share the same domain, then your configuration is invalid. My version of config file is below.
P.S. I created separate module Sample, so it would not interfere with Application config.
use Zend\Router\Http\Literal;
use Zend\Router\Http\Segment;
use \Sample\Controller\IndexController;
use Zend\ServiceManager\Factory\InvokableFactory;
return [
'router' => [
'routes' => [
'v2' => [
'type' => Segment::class,
'options' => [
'route' => '/v2/app',
'defaults' => [
'controller' => IndexController::class,
'action' => 'index',
],
],
'child_routes' => [
'index' => [
'type' => Literal::class,
'options' => [
'route' => '/',
'defaults' => [
'controller' => IndexController::class,
'action' => 'index',
],
],
],
'application' => [
'type' => Segment::class,
'options' => [
'route' => '[/:action]',
'defaults' => [
'controller' => IndexController::class,
'action' => 'index',
],
],
]
],
],
],
],
'controllers' => [
'factories' => [
IndexController::class => InvokableFactory::class,
],
],
'view_manager' => [
'template_path_stack' => [
__DIR__ . '/../view',
],
],
];
This is how you create links to that application:
<?php echo $this->url("v2/application", ['action' => 'page-name']) ?>
In ZF2 a route maps to a specific controller class and its action or method, so they can be of any modules. This route graph/page-name may map to GraphController and its method pageNameAction(), for example. But that can be any other controller and its actions. This depends on your needs.
Now lets come to the type of a route. There are some route plugins in ZF. These can be used to specify a route's type. For example, Hostname specifies domain name and subdomain like subdomain.domain.tld. But you are using domain.tld/v2/app. Hostname type does not allow / in the 'route' => 'subdomain.domain.tld'. So this will not work, in this case.
You can get this type of endpoints (domain.tld/v2/app) without creating any subdomain that you said in your thread. That is why MVC pattern, Framework is more convenient. You can create a lot of endpoints for different purposes(domain.tld/v1/abc domain.tld/v2/mno domain.tld/v3/xyz etc) in your ZF application. They may be mapped to different controllers and actions from different modules.
So you do not need to be dependent on the subdomains as the way you asked.
You also said that you need domain.tld/graph/page-name which actually works as domain.tld/v2/app/graph/page-name. This means you want to hide this part v2/app from the url if I am not wrong. But this is still a challenge.
If you have it graph/page-name used under this v2/app/graph/page-name you may define your route as follows
// Top key of a route
'v2app' => [
'type' => 'Segment',
'options' => [
'route' => '/v2/app',
'defaults' => [
'__NAMESPACE__' => 'Application\Controller',
'controller' => 'Index',
'action' => 'index',
],
],
'may_terminate' => true,
'child_routes' => [
'default' => [
'type' => 'Segment',
'options' => [
'route' => '/',
'defaults' => [
'controller' => 'Application\Controller\Index',
'action' => 'index',
],
],
],
'graph' => [
'type' => 'Segment',
'options' => [
'route' => '[/:controller/:action]',
'defaults' => [
'controller' => 'Application\Controller\Graph',
'action' => 'pageName',
],
],
],
],
],
You can add this to any route section of any module.config.php by correcting the module name, controller name etc. But keep it mind that same configuration for multiple routes would make you unhappy.
Now to output those routes use the following snippet of code
echo $this->url('v2app/graph', [], [], true);
echo $this->url(null, ['controller' => 'graph', 'action' => 'page-name']);
// outputs
// /v2/app/graph/page-name
while v2app is your top route key and graph is the child route of it.
Now you have said about how you should implement links. If you want to show them as menu, you do not have to code like that you said. You can follow this to implement links as menu. But keep it mind providing value (the top key of a route) for the route key while building an array for the navigation.
The following example from above for this route graph/page-name
[
'label' => 'Graph',
// route name not route pattern
'route' => 'graph', // not /graph/page-name
],
Otherwise if you want to show a link in the body of a page you then use $this->url() helper as used above.
Please refer to this DOC for Hostname type.
If we can not help you here please ask this here
Just create a cutomized ViewModel that will contain own url implementation
/**
* #param \Zend\Mvc\MvcEvent $e The MvcEvent instance
* #return void
*/
public function setLayout($e)
{
// Should return your customization
$viewModel = $e->getViewModel();
In that case all your $this->url() will do directly the same trick as:
$this->url('application/default', array('controller' => 'graph', 'action' => 'page-name')
As a partial example to customize render:
$customStrategy = $app->getServiceManager()->get('{Your class name}');
$view->getEventManager()->attach($customStrategy, 1); // 1 - means priority
Inside your class
preg_replace_callback('/<a href=\\"([^\\"]*)\\">(.*)<\\/a>/iU', function ($matches) {
// do whatever you need for each URL
}, $content);
Or it can be used DOM object (but it's relying on your code validity):
$xpath = new \DOMXPath((new \DOMDocument)->loadHTML($content));
foreach ($xpath->query('//a[href]') as $link) {
// ...
}
There is a way around for this purpose. You may try this one. To do that you need to create a custom route plugin and then set that to route pointing to the Graph controller and its target method pageNameAction(), in this case. Please follow the steps below.
Here graph/page-name would work as v2/app/graph/page-name meaning the following link would work as you expect.
FooBar
Please take the custom route plugin from here. Put it under this src/YourModuleName/Route directory using the namespace YourModuleName\Route.
Notice you need to replace 'template_prefix' => 'yourmodulename', with your own module name with all small letters. View templates will be searched for by matching this patten yourmodulename/controller/action-name behind the scene.
'v2app' => [
'type' => 'Segment',
'options' => [
'route' => '[/v2/app]',
'defaults' => [
'controller' => 'YourModuleName\Controller\Index',
'action' => 'index',
],
],
'may_terminate' => true,
'child_routes' => [
'graph' => [
// We call the custom route plugin thus
'type' => 'YourModuleName\Route\AppRoute',
'options' => [
'dir_name' => __DIR__ . '/../view',
'template_prefix' => 'yourmodulename',
'filename_pattern' => '/[a-z0-9_\-]+/',
'defaults' => [
'controller' => 'YourModuleName\Controller\Graph',
'action' => 'pageName',
],
],
],
],
],
Now format the pageNameAction() as the following
public function pageNameAction()
{
// Get path to view template from route params
$pageTemplate = $this->params()->fromRoute('page', null);
// If path is not valid
if($pageTemplate == null) {
$this->getResponse()->setStatusCode(404);
return;
}
$viewModel = new ViewModel();
// Here we set template to be rendered for `v2/app/graph/page-name`
$viewModel->setTemplate($pageTemplate);
return $viewModel;
}
If you need to output link in the view script use this way
echo $this->serverUrl('/graph/page-name');
Hope this would help you!
Related
I would like to add a new route (link) to my ZF2 application like following:
mysite.com/somename/?invitecode=12345
Please note that /somename/ shouldn't be controller, but merely just a name in link which is used for tracking purposes. I figured I could do this by adding a new controller, but since this name is going to be dynamic, I can't use controller for this. I have found this in the module.config.php file:
'view_manager' => array(
'display_not_found_reason' => true,
'display_exceptions' => true,
'doctype' => 'HTML5',
'not_found_template' => 'error/404',
'exception_template' => 'error/index',
'template_map' => array(
'layout/layout' => __DIR__ . '/../view/layout/layout.phtml',
'application/index/index' => __DIR__ . '/../view/application/index/index.phtml',
'error/404' => __DIR__ . '/../view/error/404.phtml',
'error/index' => __DIR__ . '/../view/error/index.phtml',
),
'template_path_stack' => array(
__DIR__ . '/../view',
),
),
I tried adding a path here like this just for testing purposes:
'application/index/indextest' => __DIR__ . '/../view/application/index/index.phtml',
And I've tried accessing the URL like this:
mysite.com/index/indextest
But the only way I can access this link is if I add an action in the controller like this:
public function indextestAction()
{
return $this->redirect()->toUrl("/");
}
Please note that the:
mysite.com/THISNAMEHERE/?invitecode=12345
Please note that THISNAMEHERE is dynamic, and varies upon what is written in my vhost's config file.
What am I supposed to do here? Can someone help me out with this please?
EDIT:
Guys I've done the following so far, I have added a new controller with a name of "InviteController" which does the following check:
public function indexAction()
{
if(!empty(htmlspecialchars($_GET["inviter"])))
{
return $this->redirect()->toUrl("/user/emailsignup");
}
}
I've added the controller to the invokables list like following:
'Application\Controller\Invite' => 'Application\Controller\InviteController',
And in my module.config.php file:
'invite' => array(
'type' => 'Zend\Mvc\Router\Http\Literal',
'options' => array(
'route' => '/invite/',
'defaults' => array(
'controller' => 'Application\Controller\Invite',
'action' => 'index',
),
),
),
So now when I try to access the URL it is like following:
mysite.com/**invite**/?inviter=12345
However this is still not what I want... I need this bolded part (INVITE) to be dynamic. Basically if I'm accessing the app from a different vhost it would be like this:
mysite.com/vhost1name/?inviter=1234
And I'd still like it to invoke the InviteController and Index action within that controller.
Edit #2: Finally solved it! Thanks to #Wilt for the links and explanation! :)
Just in case anyone wonders, this is the solution:
'application' => array(
'type' => 'Segment',
'options' => array(
'route' => '/[:controller[/:action]]',
'constraints' => array(
'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
),
'defaults' => array(
'__NAMESPACE__' => 'Application\Controller',
'controller' => 'Index',
'action' => 'index',
),
),
'may_terminate' => true,
'child_routes' => array(
'default' => array(
'type' => 'Segment',
'options' => array(
'route' => '/[:controller[/:action]]',
'constraints' => array(
'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
),
'defaults' => array(
'controller' => 'Application\Controller\Invite',
'action' => 'index',
),
),
),
),
The routing for your ZF2 application is configured in your router config. The router config for a ZF2 module is most commonly stored inside the module.config.php file inside the module config folder:
/module/MyModuleName/config/module.config.php
The router config looks like this:
// This is your router config
'router' => array(
'routes' => array(
// config goes here
),
),
You can read more on routing in the ZF2 documentation chapter Routing and controllers
What you refer to in your question is a view_manager config not a router config.
If you are not familiar with basic concepts like routing and router config I would strongly suggest to follow a tutorial to get to know these basics before you work on your own application. The ZF2 tutorial skeleton application (also known as the album application) is a good starting point. Following the steps in the tutorial will help you achieve what you want.
Why is Zend 2 such a !##(#(!##??
OK, so I'm trying to get a simple redirect working. I have a controller called 'listitems' with an action called 'editlistitem'. After hours of banging on it with a hand sledge, I've finally got the form to work and the validation to work and the hydration to Doctrine to work so I can save the result.
The last step is to redirect the user to the 'showlistitem' action which includes the id trailing it. (full route sub path is 'listitem/showlistitem/2' where 2 is the id I want to see)
I have tried:
$this->redirect()->toRoute('/listitem/showlistitem/2');
$this->redirect()->toRoute('listitem/showlistitem/2');
$this->redirect()->toRoute('showlistitem/2');
$this->redirect()->toRoute('listitem/showlistitem', array('id' => 2));
$this->redirect()->toRoute('listitem-showlistitem', array('id' => 2));
None of them flippin work! (they all return route not found)
A route to the controller is in modules.config.php with a child route to the action. I can go directly to the url by typing it in manually and it works fine. How in the bleep do I get Zend to redirect the user to that route from an action?
The toRoute method provided by the The Redirect plugin needs the route name to be passed as parameter. This is its desciption :
toRoute(string $route = null, array $params = array(), array $options = array(), boolean $reuseMatchedParams = false)
Redirects to a named route, using the provided $params and $options to assembled the URL.
Given this simple route configuration example :
//module.config.php
'router' => array(
'routes' => array(
'home' => array(
'type' => 'Segment',
'options' => array(
'route' => '/',
'defaults' => array(
'controller' => 'index',
'action' => 'index',
),
),
),
'app' => array(
'type' => 'Literal',
'options' => array(
'route' => '/app',
'defaults' => array(
'controller' => 'index',
'action' => 'index',
),
),
'may_terminate' => true,
'child_routes' => array(
'default' => array(
'type' => 'Segment',
'options' => array(
'route' => '/[:controller[/:action[/:id]]]',
'constraints' => array(
'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id'=>'[0-9]+',
),
),
),
),
),
),
),
This redirection works :
return $this->redirect()->toRoute('app/default',
array('controller'=>'controller-name', 'action'=>'action-name', 'id'=>$id));
In your case, this would work :
return $this->redirect()->toRoute('app/default',
array('controller'=>'listitem', 'action'=>'showlistitem', 'id'=>2));
After searching a long time with no success.
before I give up, I would like to ask:
Is there a way to route a subdomain to a module in Zend Framework 2? like:
Subdomain => Module
api.site.com => api
dev.site.com => dev
admin.site.com => admin
site.com => public
...
I tried doing it like this but I can't get access to controllers other than the default (Index).
'router' => array(
'routes' => array(
'home' => array(
'type' => 'Hostname',
'options' => array(
'route' => 'site.com',
'defaults' => array(
'__NAMESPACE__' => 'Application\Controller',
'controller' => 'Index',
'action' => 'index',
),
)
)
),
),
Thank you for taking the time to help me.
Zend Framework 2 doesn't have a notion of routing to modules; all routing mappings are between a URI pattern (for HTTP routes) and a specific controller class. That said, Zend\Mvc provides an event listener (Zend\Mvc\ModuleRouteListener) which allows you to define a URI pattern that maps to multiple controllers based on a given pattern, and so emulates "module routing". To define such a route, you would place this as your routing configuration:
'router' => array(
'routes' => array(
// This defines the hostname route which forms the base
// of each "child" route
'home' => array(
'type' => 'Hostname',
'options' => array(
'route' => 'site.com',
'defaults' => array(
'__NAMESPACE__' => 'Application\Controller',
'controller' => 'Index',
'action' => 'index',
),
),
'may_terminate' => true,
'child_routes' => array(
// This Segment route captures the requested controller
// and action from the URI and, through ModuleRouteListener,
// selects the correct controller class to use
'default' => array(
'type' => 'Segment',
'options' => array(
'route' => '/[:controller[/:action]]',
'constraints' => array(
'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
),
'defaults' => array(
'controller' => 'Index',
'action' => 'index',
),
),
),
),
),
),
),
(Click here to see an example of this # ZendSkeletonApplication)
This is only half of the equation, though. You must also register every controller class in your module using a specific naming format. This is also done through the same configuration file:
'controllers' => array(
'invokables' => array(
'Application\Controller\Index' => 'Application\Controller\IndexController'
),
),
The array key is the alias ModuleRouteListener will use to find the right controller, and it must be in the following format:
<Namespace>\<Controller>\<Action>
The value assigned to this array key is the fully-qualified name of the controller class.
(Click here to see an example of this # ZendSkeletonApplication)
NOTE: IF you aren't using ZendSkeletonApplication, or have removed it's default Application module, you will need to register the ModuleRouteListener in one of your own modules. Click here to see an example of how ZendSkeletonApplication registers this listener
If i understand slide #39 of DASPRIDS Rounter Presentation correctly, it's as simple as - on a per module basis - to define your subdomain hosts, i.e.:
'router' => array(
'routes' => array(
'home' => array(
'type' => 'Hostname',
'options' => array(
'route' => 'api.site.com',
'defaults' => array(
'__NAMESPACE__' => 'Api\Controller',
'controller' => 'Index',
'action' => 'index',
),
)
)
),
),
Etc, you'd do this for every Module on its own.
How to allow all sub actions inside that controller with one router rule? For example this follow:
visit: site/login - works only
site/login/forgetpassword - does not work
site/login/remmeberme - does not work
Example:
$router = $e->getApplication()->getServiceManager()->get('router');
$route = Http\Literal::factory(array(
'route' => '/login',
'defaults' => array(
'controller' => 'Application\Controller\Login',
'action' => 'index'
),
));
$router->addRoute('login', $route, null);
Follow up:
How can i make it so that /login and /login/anything works?
$route = Http\Segment::factory(array(
'route' => '/login[/:action]',
'defaults' => array(
'controller' => 'Application\Controller\Login',
'action' => 'index'
),
));
$router->addRoute('login', $route, null);
There is an excellent QuickStart Tutorial available within the official Documentation. Set up your route like the following to be allowed multiple actions and an ID Parameter. Fur further information please take a look at the documentation.
You may also be interested in DASPRiDs presentation from ZendCon2012
'router' => array(
'routes' => array(
'album' => array(
'type' => 'segment',
'options' => array(
'route' => '/album[/:action][/:id]',
'constraints' => array(
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'Album\Controller\Album',
'action' => 'index',
),
),
),
),
),
Build navigation from config:
'navigation' => array(
'default' => array(
'admin' => array(
'label' => 'Administration',
'controller' => 'index',
'action' => 'index',
'route' => 'admin/default',
),
'album' => array(
'label' => 'Album',
'controller' => 'index',
'action' => 'index',
'route' => 'album/default',
),
/* ... */
Routing is configured like it is true. Navigation in the menu works. Links menu lead to the desired controller/action of the desired module. But while introducing menu and a transition to one or another menuitem, active marked both points simultaneously and 'Administration' and 'Album'. As I understand it, for the reason that match the names of controllers and actions with them, but there's still the 'route' and it's different... not for nothing that the generated different url for each item... but somehow, despite this, they both are marked as active.
Routing config:
'router' => array(
'routes' => array(
'admin' => array(
'type' => 'Literal',
'options' => array(
'route' => '/admin',
'defaults' => array(
'__NAMESPACE__' => 'Admin\Controller',
'controller' => 'Index',
'action' => 'index',
),
),
'may_terminate' => true,
'child_routes' => array(
'default' => array(
'type' => 'Segment',
'options' => array(
'route' => '/[:controller][/:action[/id:id]]',
'constraints' => array(
'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
),
'defaults' => array(
),
),
),
Album routing config similar...
Why this is happening? Thanks.
Looks like it's how ZF2 works (read isActive() function in Zend\Navigation\Page\Mvc.php). Initially it checks matching of route/controller/action, but if it fails, ZF2 again check for just controller/action pair. So there are three possible ways:
Open a ticket at https://github.com/zendframework/zf2/issues and wait for response.
Override \Zend\Navigation\Page\Mvc.
Choose different names for controllers (and don't use index name because it's default name for controller in Mvc.php).
If you make your controller names include the the namespace then they will be unique and won't clash:
Admin\Controller\IndexController
Album\Controller\IndexController
Rather than
Index
Index