MVC routing using Symfony routing - php

Using symfony / routing, need to implement routing for the MVC application. Using the whole Symfony is prohibited, only the library.
Controller class:
namespace App\Controllers;
use App\Core\Controller;
class IndexController extends Controller {
public function IndexAction(){
$this->View->render('index');
}
}
view class:
namespace App\Core;
namespace App\Core;
class View{
public function render($viewName) {
$viewAry = explode('/', $viewName);
$viewString = implode(DS, $viewAry);
if(file_exists('View/site' . $viewString . '.php')) {
require 'View/site' . $viewString . '.php';
} else {
die('The view \"' . $viewName . '\" does not exist.');
}
}
}
and Index.php itself from which it all starts:
use App\Controllers\IndexController;
use App\Core\Routing;
use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
use App\Core\Application;
use Symfony\Component\Routing\Router;
require __DIR__ . '/vendor/autoload.php';
$collection = new RouteCollection();
$collection->add('index', new Route('/', array(
'_controller' => [IndexController::class, 'IndexAction']
)));
return $collection;
As a result of a request to the application through the postman, I get nothing, what's the problem?

In your front controller you are just defining the routes, but not actually processing the request, matching it to a controller or invoking it.
There is a section on this topic in the manual, it uses more symfony components, but can be of help.
You'd have to determine the requested route directly from PATH_INFO instead of using the HttpFoundation component, and then try to match the request to a route.
Here is a very crude implementation:
$collection = new RouteCollection();
$collection->add('index', new Route('/', array(
'_controller' => [IndexController::class, 'IndexAction']
)));
$matcher = new UrlMatcher($collection, new RequestContext());
// Matcher will throw an exception if no route found
$match = $matcher->match($_SERVER['PATH_INFO']);
// If the code reaches this point, a route was found, extract the corresponding controller
$controllerClass = $match['_controller'][0];
$controllerAction = $match['_controller'][1];
// Instance the controller
$controller = new $controllerClass();
// Execute it
call_user_func([$controller, $controllerAction]);

Related

Lazy initialization of objects in laravel container

There is a custom project in which I decided to use the DI container from Laravel, here is the code:
bootstrap:
<?php
use Illuminate\Container\Container;
use App\Providers\AppServiceProvider;
require_once __DIR__ . '/../vendor/autoload.php';
$container = new Container;
$appServiceProvider = new AppServiceProvider($container);
$appServiceProvider->register();
return $container;
AppServiceProvider:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(CallbackApplication::class);
$this->app->bind(SMSSubscriptionService::class);
$this->app->bind(HttpClient::class);
$this->app->bind(DriverMysqli::class);
$this->app->bind(Response::class);
$this->app->bind(DatabaseInterface::class, function ($app) {
$db = $app->make(DriverMysqli::class);
$db->setConnectionConfig(DatabaseConfig::PARAMS['local']);
$db->initialize();
return $db;
});
$this->app->instance(Request::class, Request::capture());
}
public function boot()
{
}
}
Project entry point:
$container = require __DIR__ . '/../Core/bootstrap2.php';
/** #var CallbackApplication $controller */
$controller = $container->get(CallbackApplication::class);
$controller->init();
$controller->process();
$controller->save();
$controller->response();
I used PHP DI before (https://php-di.org/)
But now I decided to switch to the Laravel container
The PHP DI documentation says:
PHP-DI always creates objects only when they are requested or injected somewhere.
Do laravel containers also use lazy object initialization?
If I don't need to use for example the Request object in my project entry point, then it won't be initialized in the container?
I searched everywhere, I did not find the answer, can I somehow check this?

Get Slim with PHP-DI (autowiring) working

I tried to get slim with PHP-DI and Autowiring working, without success. Maybe I have a wrong understanding of autowiring.
I setup a new project and created the following Index.php:
require __DIR__ . '/../vendor/autoload.php';
use userservice\core\services\UserServiceInterface;
use userservice\infrastructure\services\UserService;
use userservice\webservice\controller\UsersController;
use DI\Container;
$app = \DI\Bridge\Slim\Bridge::create();
$container = $app->getContainer();
$container->set(UserServiceInterface::class, UserService::class);
$container->set(UsersController::class, UsersController::class);
$app->get('/user', [UsersController::class, 'get']);
$app->run();
The UsersController
namespace userservice\webservice\controller;
use userservice\core\services\UserServiceInterface;
class UsersController{
/**
*
* #var UserServiceInterface
*/
private $userService;
public function __construct(UserServiceInterface $userService) {
$this->userService = $userService;
}
public function get(\Psr\Http\Message\ResponseInterface $response, \Psr\Http\Message\RequestInterface $request){
//$user = $this->userService->get();
$response->getBody()->write("Test");
return $response;
}
}
In the example above I get the following error-message:
Non-static method userservice\webservice\controller\UsersController::get() should not be called statically in
I found a working solution how I can use PHP-DI autowiring with mapping to interfaces:
require __DIR__ . '/../vendor/autoload.php';
use userservice\core\services\UserServiceInterface;
use userservice\infrastructure\services\UserService;
use userservice\webservice\controller\UsersController;
use userservice\core\repositories\UserRepositoryInterface;
use userservice\infrastructure\repositories\UserRepository;
/**
* Mapping of classes to interfaces
*/
$definitions = [
//REPOSITORIES
UserRepositoryInterface::class => DI\get(UserRepository::class),
//SERVICES
UserServiceInterface::class => DI\get(UserService::class),
];
$builder = new DI\ContainerBuilder();
$builder->addDefinitions($definitions);
$container = $builder->build();
$app = \DI\Bridge\Slim\Bridge::create($container);
$app->get('/user', [UsersController::class, 'get']);
$app->run();

PHP Slim 3 - Accessing class object instances within a slim route

So I’m learning how to write a Slim 3 PHP authentication app and I'm using an example code structure to get me started. The example code has a file called dependencies.php which has a series of functions that create object instances of other classes. These are then assigned to a $container variable with a name for each function. An example of these functions in the dependencies.php file can be seen here:
$container['view'] = function ($container) {
$view = new \Slim\Views\Twig(
$container['settings']['view']['template_path'],
$container['settings']['view']['twig'],
[
'debug' => true // This line should enable debug mode
]
);
$basePath = rtrim(str_ireplace('index.php', '', $container['request']->getUri()->getBasePath()), '/');
$view->addExtension(new Slim\Views\TwigExtension($container['router'], $basePath));
$view->addExtension(new \Twig_Extension_Debug());
return $view;
};
$container['validate_sanitize'] = function ($container)
{
$class_path = $container->get('settings')['class_path'];
require $class_path . 'ValidateSanitize.php';
$validator = new ValidateSanitize();
return $validator;
};
$container['hash_password'] = function($container)
{
$class_path = $container->get('settings')['class_path'];
require $class_path . 'HashPassword.php';
$hash = new HashPassword();
return $hash;
};
These functions are then somehow called in my slim routes. For example in my register.php slim route the validate_sanitize class object is called with a simple
$this->get(‘validate_sanitize’);
and assigned to a variable which I can then use to call methods from the validate_sanitize class.
However what I don’t understand is how this get method works on calling a class object from the dependencies.php file.
Here is the aforementioned register route that is a post request for incoming form data:
$app->post('/register', function(Request $request, Response $response)
{
$arr_tainted_params = $request->getParsedBody();
$sanitizer_validator = $this->get('validate_sanitize'); //here for example
$password_hasher = $this->get('hash_password');
$tainted_email = $arr_tainted_params['email'];
$tainted_username = $arr_tainted_params['username'];
$tainted_password = $arr_tainted_params['password'];
$model = $this->get('model');
$sql_wrapper = $this->get('sql_wrapper');
$sql_queries = $this->get('sql_queries');
$db_handle = $this->get('dbase');
$cleaned_email = $sanitizer_validator->sanitize_input($tainted_email, FILTER_SANITIZE_EMAIL);
$cleaned_username = $sanitizer_validator->sanitize_input($tainted_username, FILTER_SANITIZE_STRING);
$cleaned_password = $sanitizer_validator->sanitize_input($tainted_password, FILTER_SANITIZE_STRING);
});
All my routes are contained within a routes.php file that looks like this:
<?php
require 'routes/change_password.php';
require 'routes/forgot_password.php';
require 'routes/homepage.php';
require 'routes/login.php';
require 'routes/logout.php';
require 'routes/register.php';
There is also a bootstrap file that creates a new Slim container, Slim App instance and also includes necessary files. I’m also not entirely sure what a Slim\Container is or what it does. This bootstrap file looks like this:
<?php
session_start();
require __DIR__ . '/../vendor/autoload.php';
$settings = require __DIR__ . '/app/settings.php'; //an array of options containing database configurations and the path to twig templates
$container = new \Slim\Container($settings); //not sure what this does
require __DIR__ . '/app/dependencies.php';
$app = new \Slim\App($container);
require __DIR__ . '/app/routes.php';
$app→run();
I’ve tried reading a number of articles as well as watching various YouTube videos but they do things differently with controllers which just adds more complexity and confusion for me. I’d much rather an explanation on this specific example as I find the code structure fairly simple.
Thanks.
Inside route callable, $this will point to $container instance.
In Slim 3.0, if you take a look at map() method of Slim\App class, you will see following code:
if ($callable instanceof Closure) {
$callable = $callable->bindTo($this->container);
}
bindTo() is what makes you have access to container inside route callable using $this variable.
If you want to use class as route handler and want access to container instance inside class, you need to pass it manually. For example
<?php
namespace App\Controller;
class MyPostRegisterController
{
private $container;
public function __constructor($container)
{
$this->container = $container;
}
public function __invoke(Request $request, Response $response)
{
$sanitizer_validator = $this->container->get('validate_sanitize');
//do something
}
}
Then you can define route as following
$app->post('/register', App\Controller\MyPostRegisterController::class);
If Slim can not find MyPostController class in dependency container, it tries to create them and pass container instance.
Update
To call other method, append method name after class name separated by colon. For example, following route registration will call method home() in MyPostRegisterController class.
$app->post('/register', App\Controller\MyPostRegisterController::class . ':home');

Dependency injection not working with League\Route and League\Container

I'm building a web application right now and I'm facing a problem with my controller.
I want to send to my controller my League\Plate\Engine (registred in my Container) but I keep having the same error : Argument 3 passed to App\Controller\Main::index() must be an instance of League\Plates\Engine, array given
Here is my files :
dependencies.php
use League\Container\Container;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Yajra\Pdo\Oci8;
use League\Container\ReflectionContainer;
$container = new Container();
// Active auto-wiring
$container->delegate(
new ReflectionContainer
);
// Others dependencies
// ...
// Views
$container->add('view', function () {
$templates = new League\Plates\Engine();
$templates->addFolder('web', __DIR__ . '/templates/views/');
$templates->addFolder('emails', __DIR__ . '/templates/emails/');
// Extension
//$templates->loadExtension(new League\Plates\Extension\Asset('/path/to/public'));
//$templates->loadExtension(new League\Plates\Extension\URI($_SERVER['PATH_INFO']));
return $templates;
});
return $container;
routes.php
use League\Route\RouteCollection;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
$route = new RouteCollection($container);
// Page index
$route->get('/', 'App\Controller\Main::index');
// Others routes...
return $route;
Main.php
namespace App\Controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use League\Plates\Engine;
class Main
{
public function index(ServerRequestInterface $request, ResponseInterface $response, Engine $templates) {
//return $response->getBody()->write($this->templates->render('web::home'));
return $response;
}
}
Thank you in advance
EDIT
I've made a progress.
I extended the Main class to extends the abstract class BaseController which looks like this :
namespace App\Controller;
use League\Plates\Engine;
class BaseController
{
protected $templates;
public function __construct(Engine $templates) {
$this->templates = $templates;
}
}
The first error goes away, but another one show up. In the Main class, I would like to use the view object that I instanciate in the container, but the object passed to the constructor is an empty one :
Main.php
class Main extends BaseController
{
public function index(ServerRequestInterface $request, ResponseInterface $response) {
echo '<pre>'.print_r($this->templates,1).'</pre>'; // Return an empty Plate Engine object
return $response->getBody()->write($this->templates->render('web::home'));
//return $response;
}
}
And this doesn't explain why the first error shows up
EDIT 2
After some digging, I finally make it works, but I sense that something is wrong.
I replaced in the container the term view by the namespace of the Engine class :
$container->add('League\Plates\Engine', function () {
// The same as before
});
In Main.php I've updated the index function like this :
public function index(ServerRequestInterface $request, ResponseInterface $response) {
$body = $response->getBody();
$body->write($this->templates->render('web::home'));
return $response->withBody($body);
}
And the page doesn't throw a 500 error and the html file is displayed correctly.
But, what if I want to change the template engine by Twig for example ? This would mean I'll need to change all the call to $container->get('League\Plate\Engine'); by $container->get('What\Ever'); ? That's not very practical!
I probably missed something!
And the problem will rise again when I'll want to use my PDO object... or every other object.
Ok so I solved my problem by registering my Controllers classes in the container itself.
For example, for displaying the index page, the Main class call the index function. In my container, I call
$container->add('App\Controller\Main')
->withArgument($container->get('view'));
To summary :
bootstap.php (called by index.php)
require __DIR__ . '/../../vendor/autoload.php';
$dotenv = new \Dotenv\Dotenv(__DIR__ . '/../');
$dotenv->load();
$config = new Config(__DIR__ . '/../config/');
$container = require __DIR__ . '/../dependencies.php';
$route = require __DIR__ . '/../routes.php';
$response = $route->dispatch($container->get('request'), $container->get('response'));
$container->get('emitter')->emit($response);
dependencies.php
$container = new Container();
// activate auto-wiring
$container->delegate(
new ReflectionContainer
);
// Others dependencies...
// Views
$container->add('view', function () {
$templates = new League\Plates\Engine();
$templates->addFolder('web', __DIR__ . '/templates/views/');
$templates->addFolder('emails', __DIR__ . '/templates/emails/');
// Extension
//$templates->loadExtension(new League\Plates\Extension\Asset('/path/to/public'));
//$templates->loadExtension(new League\Plates\Extension\URI($_SERVER['PATH_INFO']));
return $templates;
});
// THIS IS THE TRICK
$container->add('App\Controller\Main')
->withArgument($container->get('view'));
// others controllers...
return $container;
routes.php
$route = new RouteCollection($container);
// Page index
$route->get('/', 'App\Controller\Main::index');
// Others routes...
return $route;
I have no idea why or how this work !
This works, because you are registering with the container your controller class, and telling the container to dependency inject your View class in the constructor..
The line you added, here, is doing that:
$container->add('App\Controller\Main')
->withArgument($container->get('view'));
The Hello World documentation example explains it perfectly.
http://container.thephpleague.com/3.x/constructor-injection/

Laravel 5 Override a class method

I would like to override the method setContent() of the class Illuminate\Http\Response as :
<?php
namespace MyNameSpace\MyClass;
use Illuminate\Http\Response;
class myResponse extends Reponse {
public function setContent($content)
// Something
}
}
But I don't know how to tell Laravel to load my class instead of the original one.
Too late, but as i came up with same issue. but for reference i would want to post how i resolved this issue.
When i wanted to handle all the the response by myself without using a response macro or transformer and TO OVERRIDE MANY OTHER FRAMEWORK DEFAULT METHODs . this is how i completely took control of the response object.
just posted here for reference as i in my opinion it solves the
problem in a clearer way.
Lot of overriding as its done through pipeline and routing and so it`s registered as base service provider. here is how i managed to override all.
here I am using laravel 5.3
1 - create a new response class
<?php
namespace App\Extensions\Illuminate\Http;
// use Illuminate\Http\Response as BaseResponse;
use Symfony\Component\HttpFoundation\Response as BaseResponse;
class Response extends BaseResponse
{
public function setContent($content)
{
//do what ever you want to do with the content
//dd($content);
}
}
2 - create a new router and use the new response
<?php
namespace App\Extensions\Illuminate\Routing;
use Illuminate\Http\Request;
use Illuminate\Routing\Events\RouteMatched;
use Illuminate\Routing\Router as IlluminateRouter;
use App\Extensions\Illuminate\Http\Response;
class Router extends IlluminateRouter
{
public function prepareResponse($request, $response)
{
if ($response instanceof PsrResponseInterface) {
$response = (new HttpFoundationFactory)->createResponse($response);
} elseif (! $response instanceof SymfonyResponse) {
$response = new Response($response);
}
return $response->prepare($request);
}
}
3 - create new routing service provider use new router
<?php
namespace App\Providers;
use Illuminate\Routing\RoutingServiceProvider as ServiceProvider;
use App\Extensions\Illuminate\Routing\Router;
class RoutingServiceProvider extends ServiceProvider
{
protected function registerRouter()
{
$this->app['router'] = $this->app->share(function ($app) {
return new Router($app['events'], $app);
});
}
}
4 - create new Application class and use new routing service provider
<?php
namespace App\Extensions\Illuminate\Foundation;
use Illuminate\Events\EventServiceProvider;
use Illuminate\Foundation\Application as App;
use App\Providers\RoutingServiceProvider;
class Application extends App
{
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
}
5 - and finally in bootstrap\app.php use the new Application
// $app = new Illuminate\Foundation\Application(
// realpath(__DIR__.'/../')
// );
$app = new App\Extensions\Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
You will need to extend the Response facade to reflect the class you have, then change your applications /config/app.php to link to your new facade rather than laravels.
You need to create a facade like so
<?php namespace App\Facades;
use Illuminate\Support\Facades\Response as BaseResponse;
class Response extends BaseResponse {
public static function overwriteMethod()
{
//
}
}
Then go to config/app.php under facades comment out this line
//'Response' => 'Illuminate\Support\Facades\Response',
Then add this to the facades stack
'Response' => 'App\Facades\Response',

Categories