Routing based on standard PHP query string - php

As you know, Zend Framework (v1.10) uses routing based on slash separated params, ex.
[server]/controllerName/actionName/param1/value1/param2/value2/
Queston is: How to force Zend Framework, to retrive action and controller name using standard PHP query string, in this case:
[server]?controller=controllerName&action=actionName&param1=value1&param2=value2
I've tried:
protected function _initRequest()
{
// Ensure the front controller is initialized
$this->bootstrap('FrontController');
// Retrieve the front controller from the bootstrap registry
$front = $this->getResource('FrontController');
$request = new Zend_Controller_Request_Http();
$request->setControllerName($_GET['controller']);
$request->setActionName($_GET['action']);
$front->setRequest($request);
// Ensure the request is stored in the bootstrap registry
return $request;
}
But it doesn't worked for me.

$front->setRequest($request);
The line only sets the Request object instance. The frontController still runs the request through a router where it gets assigned what controller / action to call.
You need to create your own router:
class My_Router implements Zend_Controller_Router_Interface
{
public function route(Zend_Controller_Request_Abstract $request)
{
$controller = 'index';
if(isset($_GET['controller'])) {
$controller = $_GET['controller'];
}
$request->setControllerName($controller);
$action = 'index';
if(isset($_GET['action'])) {
$action = $_GET['action'];
}
$request->setActionName($action);
}
}}
Then in your bootstrap:
protected function _initRouter()
{
$this->bootstrap('frontController');
$frontController = $this->getResource('frontController');
$frontController->setRouter(new My_Router());
}

Have you tried: $router->removeDefaultRoutes(), then $request->getParams() or $request->getServer()?

Related

PHP: Simple Routing class, question how to add mutiple routes

I try to work with a simple Router class (learning basics before a framework, but I think I got something wrong with the example router I used. Below is a very small router class I got from a colleague and I tried to integrate it into my code to substitute previous uses where I just used echo before (commented out part of the code).
both loginController showLoggedInUser() and registerController index() are just used to render an html template.
Both $router->add() would work if I use it just to add a single route, however my router does not save multiple routes in the array because it seems every route will be saved under the key '/' and in case I provide mutiple routes it seems my previous routes are simply overwritten. So I guess I would need to adjust the Router class. How can I fix this?
PHP 7.4 used
Router.php
<?php
declare(strict_types=1);
class Router
{
private array $route;
public function add(string $url, callable $method): void
{
$this->route[$url] = $method;
}
public function run()
{
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if(!array_key_exists($path, $this->route))
{
exit();
}
return call_user_func($this->route[$path]);
}
}
index.php
<?php
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
session_start();
$router = new Router();
$mysqliConnection = new MysqliConnection();
$session = new SessionService();
$loginController = new Login($mysqliConnection);
$router->add('/', [$loginController, 'showLoggedInUser']);
//echo $loginController->showLoggedInUser();
$registerController = new Register($mysqliConnection);
$router->add('/', [$registerController, 'index']);
//echo $registerController->index();
echo $router->run();
Not sure of the overall principle of having two routes with the same name, but you could achieve this using a list of callables for each route.
I've made some changes (including the callable passed for each route) to show the principle, but you should get the idea...
class Router
{
private array $route;
public function add(string $url, callable $method): void
{
$this->route[$url][] = $method;
}
public function run()
{
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if(!array_key_exists($path, $this->route))
{
exit();
}
foreach ( $this->route[$path] as $paths ) {
$paths();
}
// Not sure what to return in this case.
// return call_user_func($this->route[$path]);
}
}
$router = new Router();
// $mysqliConnection = new MysqliConnection();
// $session = new SessionService();
// $loginController = new Login($mysqliConnection);
$router->add('/', function () { echo "login"; } );
// $registerController = new Register($mysqliConnection);
$router->add('/', function () { echo "Register"; });
echo $router->run();
I would instead recommend having separate url's, /login and /register so that they can be called separately.

PHP & MVC: Trouble dynamically creating appropriate MVC Classes

I'm trying to build a CMS using the MVC pattern. The structure of my CMS is as follows:
->index.php: entry point. Contains the appropriate includes and creates the frontController which takes a router as it's parameter:
$frontController = new FrontController(new Router);
echo $frontController->output();
->router.php: analyses the URL and defines what the names of each Model,View and Controller will be. By default they are preceded by home, but if the url is of the form http://localhost/index.php?route=Register the MVC classes will be named RegisterModel, RegisterView and Register Controller.
if(isset ( $_GET ['route'] ))
{
$this->URL = explode ( "/", $_GET ['route'] );
$this->route = $_GET ['route'];
$this->model = ucfirst($this->URL [0] . "Model");
$this->view = ucfirst($this->URL [0] . "View");
$this->controller = ucfirst($this->URL [0] . "Controller");
}
else
{
$this->model = "HomeModel";
$this->view = "HomeView";
$this->controller = "HomeController";
$this->route = "Home";
}
->frontController.php: This is where I am stuck. When I go to the homepage, it can be visualised correctly because I already have the default HomeModel,HomeView and HomeController classes created. But I created a link that points to register (localhost/index.php?route=Register) but the PHP log indicates that the appropriate Register classes weren't created by the frontController class.
class FrontController
{
private $controller;
private $view;
public function __construct(Router $router)
{
$modelName = $router->model;
$controllerName = $router->controller;
$viewName = $router->view;
$model = new $modelName ();
$this->controller = new $controllerName ( $model );
$this->view = new $viewName ( $router->getRoute(), $model );
if (! empty ( $_GET['action'] ))
$this->controller->{$_GET['action']} ();
}
public function output()
{
// This allows for some consistent layout generation code
return $this->view->output ();
}
}
At this moment I have no idea how to go about solving this issue. And even if I get the classes to be created in the frontController, is there a way to specify that the classes being dynamically generated should extend from a base Model,View,Controller class?
The default HomeView.php looks like this:
class HomeView
{
private $model;
private $route;
private $view_file;
public function __construct($route, HomeModel $model)
{
$this->view_file = "View/" . $route . "/template.php";
echo $this->view_file;
$this->route = $route;
$this->model = $model;
}
public function output()
{
require($this->view_file);
}
}
Any indications on anything that might help me get unstuck or a pointer in the right direction would be much appreciated.
EDIT1:
I forgot to add a summary of my two issues:
1. I would like to understand why the classes aren't being created in the FrontController class...
2. Once created how would I access those classes? Answer is in the comment section. Using the PHP spl_autoload_register function.
Thanks All!

Auto set view in Laravel

I want to automatically set the view on a GET request in Laravel. In the BaseController constructor I do this:
if (Request::server('REQUEST_METHOD') === 'GET')
{
$action = explode('#', Route::currentRouteAction());
$view = explode('get', $action[1]);
$view = strtolower(end($view));
$controller = strtolower(explode('Controller', $action[0])[0]);
$this->data['view'] = $controller . '.' . $view;
}
So basically if we make a request for /some/page it will look for a view file named views/some/page.blade.php.
Currently I set some data and other properties using $this->data. So I build my data up before sending the view in each method:
$this->layout->with($this->data);
I end up having the above call in EVERY GET method and would like automate this whole thing. The problem with using $this->data is that I can't access it any filters or other closures. Is there a magic method or global data store I'm not using which I could call at the end of every request and just pump out the layout?
function afterEveryThing()
{
$this->layout->with($this->data);
}
Something like the above in the BaseController or somewhere where I could do this?
Shooting from the hip here, but could you do your routing something like below. This is a bad idea to use exactly as shown, but could be a starting point for what you are trying to do.
Route::any('{controller}/{method}', function($controller, $method) {
$controllerName = ucfirst($controller) . "Controller";
$controllerObject = new $controllerName;
if (Request::server('REQUEST_METHOD') === 'GET')
{
$controllerObject->$method();
return View::make("$controller.$method")->with('data', $controllerObject->data);
}
else
{
return $controllerObject->$method();
}
});
This will work (Laravel 4)
App::after(function($request, $response)
{
//
});
Or
In any version rename actions and implement magic method __call in controller class. For example for route to "IndexController#index" action:
IndexController.php
private function __call($method, $args) {
... look for a view...
if (in_array('my_'.$method, self::$methods)) {
//call to index translated to my_index
call_user_func_array(array($this,'my_'.$method), $args);
} else {
//error no action
abort(404);
}
... after every thing ...
}
public function my_index(Request $request) {
... do action ...
}

rendering different view engine in zend depending on extension

I have a normal phtml template and another one in HAML. So somewhere in my Bootstrap.php:
protected function _initView()
{
$view = new Haml_View();
$viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer();
$viewRenderer->setView($view);
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
return $view
}
I initialize to use my Haml_View, but what i want is if the script filename has an extension .haml, it'll use Haml_view if not, then it'll use the regular Zend_view.
So i guess my question is, is there any way to find out what the current view script filename will be use?
Thanks
The basic workflow of a ZF MVC request is as follows:
Application bootstrapping
Routing
Dispatch
Zend_Application takes care of only the first item in that list,
bootstrapping. At that time, we have no idea what the request actually
is -- that happens during routing. It's only after we have routed that
we know what module, controller, and action were requested.
Source: http://weierophinney.net/matthew/archives/234-Module-Bootstraps-in-Zend-Framework-Dos-and-Donts.html
So you can't switch the view class based on script suffix in the bootstrap because the routing has not occured yet. You could do it in a FrontController plugin as early as routeShutdown, but I feel it's more natural to do it in an Action Helper. The normal methods to figure out the view script path are in Zend_View and Zend_Controller_Action_Helper_ViewRenderer. Both of these are easily available in an Action Helper.
Zend_Controller_Action_Helper_ViewRenderer is also an Action Helper and it needs to init before, so let's do our switch after the init, in the preDisptatch call of an Action Helper.
First, you need to register your helper. A good place is in the bootstrap with your view:
protected function _initView()
{
$view = new Haml_View();
$viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer();
$viewRenderer->setView($view);
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
return $view;
}
protected function _initHelpers()
{
Zend_Controller_Action_HelperBroker::addHelper(
new Haml_Controller_Action_Helper_ViewFallback()
);
}
And the helper would look like this:
class Haml_Controller_Action_Helper_ViewFallback extends Zend_Controller_Action_Helper_Abstract
{
public function preDispatch()
{
/** #var $viewRenderer Zend_Controller_Action_Helper_ViewRenderer */
$viewRenderer = $this->getActionController()->getHelper('ViewRenderer');
/** #var $view Haml_View */
$view = $viewRenderer->view;
/**
* what i want is if the script filename has an extension .haml,
* it'll use Haml_view if not, then it'll use the regular Zend_view
*/
$viewRenderer->setViewSuffix('haml');
$script = $viewRenderer->getViewScript();
if (!$view->getScriptPath($script)) {
$viewRenderer->setView(new Zend_View());
$viewRenderer->setViewSuffix('phtml');
$viewRenderer->init();
}
}
}
If there is no file with the haml extension in the default path, we assume there is one with phtml extension, and we modifiy the ViewRenderer accordingly. Don't forget to init the ViewRenderer again.
Something along the lines of:-
if(!file_exists('path/to/view.haml'){
$view = new Zend_View();
$viewRenderer->setView($view);
}
May work, although I haven't tested it.
Edit:
You could try this in your controller:-
class IndexController extends Zend_Controller_Action {
public function init()
{
$paths = $this->view->getScriptPaths();
$path = $paths[0];
$script = $path . $this->getHelper('viewRenderer')->getViewScript();
if(!file_exists($script)){
$this->viewSuffix = 'hmal';
}
}
public function indexAction()
{
}
}
I'm not 100% happy with the $path = $paths[0] bit, but I don't have time to look into it any further. Hopefully that will point you in the right direction.

How do I pass parameters to my Zend Framework plugins during instantiation?

I have defined a plugin on the libraries path, using the correct directory structure, and have made it's presence known in the application.ini file. The plugin loads, and my preDispatch() method fires. But, how can I pass parameters to the plugin during instantiation?
Here is my code:
class Project_Controller_Plugin_InitDB extends Zend_Controller_Plugin_Abstract {
private $_config = null;
public function __contruct($config){
$this->_config = $config;
}
public function preDispatch(Zend_Controller_Request_Abstract $request) {
$db = Zend_DB::factory("Pdo_Mysql", $this->_config);
Zend_Registry::set("db", $db);
}
}
Specifically, how do I pass $config to the __construct() method?
Thanks,
Solution
Here is what I ended up with (Thanks to Phil Brown!):
In my application.ini file:
autoloaderNamespaces[] = "MyProjectName_"
In my Bootstrap.php file:
protected function _initFrontControllerPlugins() {
$this->bootstrap('frontcontroller');
$frontController = $this->getResource('frontcontroller');
$plugin = new MyProjectName_Controller_Plugin_InitDB($this->_config->resources->db->params);
$frontController->registerPlugin($plugin);
}
Simply register your plugin manually in your Bootstrap class
protected function _initFrontControllerPlugins()
{
$this->bootstrap('frontcontroller');
$frontController = $this->getResource('frontcontroller');
// Config could come from app config file
// or anywhere really
$config = $this->getOption('initDb');
$plugin = new Project_Controller_Plugin_InitDB($config);
$frontController->registerPlugin($plugin);
}
Use Zend Registry
Have you read this? $Config structure resembles Zend_Config very closely, so if you wish to pass extra parameters, treat it as an array of key/values.

Categories