ZF3: Read out url parameter in PhpRenderer - php

I want to upgrade from ZF2 to ZF3 right now and has the following problem with using URL parameter in my PhpRenderer.
In ZF2 I use the HelperPluginManager to get Application, then the MvcEvent and finally the routeMatch:
$routeMatch = $this
->getHelperPluginManager()
->getServiceLocator()
->get('Application')
->getMvcEvent()
->getRouteMatch();
$parameterAction = $routeMatch->getParam('action');
In ZF3 there is a deprecation warning with using the getServiceLocator() (which makes sense, because it only returns the creationContext from the ServiceManager). I want to find a way not trigger the warning.
Configure the Application as a factory-using class (using \Zend\Mvc\Service\ApplicationFactory::class) also not works, because:
Zend\View\HelperPluginManager can only create instances of Zend\View\Helper\HelperInterface and/or callables.
Is there any way to get the Application context in my template (or better even the parameters of the URL)?

Your question title is "ZF3: Read out url parameter in PhpRenderer" while inside your question you asked another one.
You can get this in any controller (get URL parameters in ZF3):
$URLparam = (string)$this->params()->fromQuery('parameter', '');

For routeMatch, if you have a MvcEvent just use;
$event->getRouteMatch()
If you have container;
$container->getApplication()->getMvcEvent()->getRouteMatch();
If you want to access routeMatch in view there's no way except view helper like tasmaniski/zend-current-route

Related

Difference between match parameter and RequestContext in Symfony Router UrlMatcher

I don't understand the semantics of the Symfony Routing Component's API.
From the first code example on the Routing Component documentation page:
$context = new RequestContext('/');
$matcher = new UrlMatcher($routes, $context);
$parameters = $matcher->match('/foo');
Why is the hostname and HTTP method passed in via $context and the path via a parameter to match()? Or is it? There is also a path parameter in the RequestContext constructor.
One gets the impression match() is supposed to be called multiple times with different paths within one request, which I can't imagine would ever happen.
After integrating the Routing Component into my application, I now have a hunch why
it was done like that.
Most of the properties of the RequestContext - method, request body, get parameters - can be used without modification, but depending on the desired path structure and server configuration (rewrite rules, etc.) there are multiple ways in which the path needs to be preprocessed.
This doesn't explain why the path is passed to the match() function and the request object is passed to the constructor, but it does explain why they are passed in separately.

How to handle exception in Router when there's no requested class?

I am writing my own php mvc framework (just for training). The question is how to handle exception when the requested controller doesn't exist? Should I call 404 class or create and show new View from Router? I'll be glad if you have any advices for me!
Here are my autoload.php:
function __autoload($class)
{
$filename = __DIR__ . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
if (file_exists($filename))
{
require $filename;
}
else
{
throw new \Exception('The file doesn\'t exists!');
}
}
and Route.php:
namespace App;
class Route
{
public static function start ()
{
$controller_name = 'News';
$controller_action = 'Index';
if (isset($_GET['furl']))
{
// Getting rid of spaces
$url = str_replace(' ', '', $_GET['furl']);
if (substr($url, -1) == '/')
{
$url = substr($url, 0, count($url) - 2);
}
$arr = explode('/', $url);
foreach($arr as &$value)
{
$value = strtolower($value);
$value = ucfirst($value);
}
$controller_action = $arr[count($arr) - 1];
unset($arr[count($arr) - 1]);
$controller_name = implode('\\', $arr);
}
$controller_name = '\App\Controllers\\' . $controller_name;
try
{
$controller = new $controller_name();
}
catch (\Exception $e)
{
//HELP ME PLS!
}
$controller->action($controller_action);
}
}
No matter how many web frameworks, routers, autoloaders, etc are there already: keep doing what you think it's right for you and suitable to your momentarily understanding level, in order to LEARN. Actually, by confronting yourself with problems arised along the process of implementing yourself different parts of your application, you will not only gain the opportunity to learn and discover new things, but also to learn how and what to study in the already existing frameworks' design.
Study the PHP Standard Recommendations (the ones marked as "accepted"). Especially PSR-1,2,4,7. They are used by many frameworks and PHP projects. Read FAQs to find out more about the project itself.
Autoloader:
The PSR-4 provides a link with examples at the document end.
#mike suggested, that you should use the Composer autoloader. I agree with him and I strongly recommend it to you too. BUT I suggest you to do this only after you correctly implement and make use of your own autoloader (PSR-4 conform). Why? You definitely need to learn how the autoloading process works. And in some future situations you will still need your own autoloader implementation, even after Composer is installed and running.
Also be aware that you must not raise any exceptions from autoloader itself!
Router:
Btw, your class should be called "Router".
The router should not be responsible for validating the controller class/file and the action, nor for calling the action. These tasks are part of the "front controller" responsibilities. Your router should just return the components resulted after parsing, e.g. "exploding" the request URI ($_GET['furl']), in some form (as a Route object (with them as properties), as array, etc). These components are the controller name, the action name, the action parameters list (NB: the action parameters are not the query string parameters). The front controller uses them further to validate/access the controller class/file and its action and to call the action.
But please note that a router works actually in other way. In short: it matches (e.g. compares) the request method (GET, POST, etc) and the request URI against an existing (e.g. predefined by you) list of route definitions. A route definition contains the infos related to a specific controller, action, etc. If the HTTP method and the request URI "correspond" to one of the route definitions, then the router returns the matched definition's components to the front controller (in some form: as object, as array, etc).
For more details describing this principle see:
How to load classes based on pretty URLs in MVC-like page?
FastRoute
Aura.Router
Front controller:
It can be a class, but it can also be just vanilla code in the entry point of your app (index.php, bootstrap.php, etc). In the latter case, the front controller code should reside in a file outside of the document root of the app. For example in a bootstrap.php file, which is to be just included in index.php - whereas index.php resides inside the document root.
"controller/action not found" specific handling:
If a controller, or an action is not found/valid, then call a predefined action (for example displayError) of a predefined Error controller, which informs the user that, for a specific part of his request (actually of his provided request URI), no resource was found.
For example, consider that the user provided the request URI www.example.com/books/show/12. Conform to your app workflow the controller is Book, the action (e.g. the controller's method) is show and the action parameter is 12 (the value is passed as argument to the show method and defined as $bookId parameter in it). But, if the controller class is not defined, or no controller file exists, then the front controller should call the action displayError of Error controller, which should display a message like No resource found for your 'book' request. A similar info should be displayed when the show method is not yet defined in the Book controller.
Note that, if the Error controller or its action is not found/valid, then the PHP engine raises a corresponding error/exception. If you follow the next links I provided, you'll end up implementing three custom error/exception handling functions (referenced by set_error_handler, set_exception_handler and register_shutdown_function, respectively). They will catch and handle the described situation properly.
To read: Manage the errors of a framework
General error/exception handling in MVC:
Here are some good resources:
Again: Manage the errors of a framework
Error logging, in a smooth way
Error reporting basics
The (im)proper use of try..catch
Other MVC related resources:
Build a PHP MVC Application (Just for the start...)
Dependency Injection and Dependency Inversion in PHP
MVC for advanced PHP developers (A further list of resources)
Tom Butler's Programming Blog. MVC, PHP, Best practices
Clean, high quality code
P.S: Avoid the use of statics, globals, singletons. Why? Read here and here, for example.
Good luck.

Symfony2 cache and parameters

I have an odd app set up, multiple sub domains all pointing to the same Symfony2 install.
The subdomain simply changes a single parameter within the application so that different content is loaded.
This parameter is being cached however, so visiting a.site.com and then b.site.com makes A's content appear rather than B's.
Is there another approach I can use?
Is there a way I can add this parameter the cache's hash or something similar so that when the parameter changes within the app the content can change.
I am using this method to pass the constant from the sub domain into the Symfony2 application
How to pass a PHP constant as service argument in Symfony 2?
You can use Symfony's Request Object to get the URL and parse that for the subdomain. Use that to create your configuration parameter because it won't be cached.
Like this:
echo $request->getUri();
Symfony's API can be a great resource, but a bit hard to read. http://api.symfony.com/2.6/Symfony/Component/HttpFoundation/Request.html
I ended up writing a service to parse the host name, that service was then available within my Controllers.
class GameSystem {
public function getGameSystemName()
{
$ar = explode(".", $_SERVER['HTTP_HOST']);
$first = reset($ar);
$game_system_short_name = strtolower($first);
return $game_system_short_name;
}
}
Initially I had tried to use the constant value set in each subdomain's index file. But this was failing and having some really odd effects.

Symfony 2 - how to parse %parameter% in my own Yaml file loader?

I have a Yaml loader that loads additional config items for a "profile" (where one application can use different profiles, e.g. for different local editions of the same site).
My loader is very simple:
# YamlProfileLoader.php
use Symfony\Component\Config\Loader\FileLoader;
use Symfony\Component\Yaml\Yaml;
class YamlProfileLoader extends FileLoader
{
public function load($resource, $type = null)
{
$configValues = Yaml::parse($resource);
return $configValues;
}
public function supports($resource, $type = null)
{
return is_string($resource) && 'yml' === pathinfo(
$resource,
PATHINFO_EXTENSION
);
}
}
The loader is used more or less like this (simplified a bit, because there is caching too):
$loaderResolver = new LoaderResolver(array(new YamlProfileLoader($locator)));
$delegatingLoader = new DelegatingLoader($loaderResolver);
foreach ($yamlProfileFiles as $yamlProfileFile) {
$profileName = basename($yamlProfileFile, '.yml');
$profiles[$profileName] = $delegatingLoader->load($yamlProfileFile);
}
So is the Yaml file it's parsing:
# profiles/germany.yml
locale: de_DE
hostname: %profiles.germany.host_name%
At the moment, the resulting array contains literally '%profiles.germany.host_name%' for the 'hostname' array key.
So, how can I parse the % parameters to get the actual parameter values?
I've been trawling through the Symfony 2 code and docs (and this SO question and can't find where this is done within the framework itself. I could probably write my own parameter parser - get the parameters from the kernel, search for the %foo% strings and look-up/replace... but if there's a component ready to be used, I prefer to use this.
To give a bit more background, why I can't just include it into the main config.yml: I want to be able to load app/config/profiles/*.yml, where * is the profile name, and I am using my own Loader to accomplish this. If there's a way to wildcard import config files, then that might also work for me.
Note: currently using 2.4 but just about ready to upgrade to 2.5 if that helps.
I've been trawling through the Symfony 2 code and docs (and this SO question and can't find where this is done within the framework itself.
Symfony's dependency injection component uses a compiler pass to resolve parameter references during the optimisation phase.
The Compiler gets the registered compiler passes from its PassConfig instance. This class configures a few compiler passes by default, which includes the ResolveParameterPlaceHoldersPass.
During container compilation, the ResolveParameterPlaceHoldersPass uses the Container's ParameterBag to resolve strings containing %parameters%. The compiler pass then sets that resolved value back into the container.
So, how can I parse the % parameters to get the actual parameter values?
You'd need access to the container in your ProfileLoader (or wherever you see fit). Using the container, you can recursively iterate over your parsed yaml config and pass values to the container's parameter bag to be resolved via the resolveValue() method.
Seems to me like perhaps a cleaner approach would be for you to implement this in your bundle configuration. That way your config will be validated against a defined structure, which can catch configuration errors early. See the docs on bundle configuration for more information (that link is for v2.7, but hopefully will apply to your version also).
I realise this is an old question, but I have spent quite a while figuring this out for my own projects, so I'm posting the answer here for future reference.
I tried a lot of options to resolve %parameter% to parameters.yml but no luck at all. All I can think of is parsing %parameter% and fetch it from container, no innovation yet.
On the other hand I don't have enough information about your environment to see the big picture but I just come up with another idea. It can be quite handy if you declare your profiles in your parameters.yml file and load it as an array in your controller or service via container.
app/config/parameters.yml
parameters:
profiles:
germany:
locale: de_DE
host_name: http://de.example.com
uk:
locale: en_EN
host_name: http://uk.example.com
turkey:
locale: tr_TR
host_name: http://tr.example.com
You can have all your profiles as an array in your controller.
<?php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
public function indexAction()
{
$profiles = $this->container->getParameter('profiles');
var_dump($profiles);
return $this->render('AcmeDemoBundle:Default:index.html.twig');
}
}
With this approach
you don't have to code a custom YamlLoader
you don't have to worry about importing parameters into other yml files
you can have your profiles as an array anytime you have the $container in your hand
you don't have to load/cache profile files one by one
you don't have to find a wildcard file loading solution
If I got your question correctly, this approach can help you.

Instantiate a \Zend\Stdlib\RequestInterface

I want to use the method match(\Zend\Stdlib\RequestInterface) of Zend\Mvc\Router\Http\TreeRouteStack.
But I don't find any way to get the request I'm searching for.
This articles says how to get a request object outside a controler. I'm searching for an equivalent with ZF2
Finally I found how to fix by browsing the unit testing of the zf2
https://github.com/zendframework/zf2/blob/master/tests/ZendTest/Mvc/Router/Http/TreeRouteStackTest.php
The Zend\Http\PhpEnvironment\Request; will give a valid Request for the match method
use Zend\Http\PhpEnvironment\Request as PhpRequest;
/*TreeRouteStack*/
$request = new PhpRequest();
$stack->match($request);

Categories