My directory Structure is shown above. I am trying to build a Framework with Symfony Components. but one problem, when I hit a route that I defined, it doesn't give me back the response.
Here is my index.php
<?php
$loader = require 'vendor/autoload.php';
$loader->register();
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
require 'lib/Framework/Core.php';
$request = Request::createFromGlobals();
// Our Framework is now handling itself the request
$app = new Framework\Core();
$app->map('/', function () {
return new Response('This is the home page');
});
$app->map('/about', function () {
return new Response('This is the about page');
});
$response = $app->handle($request);
and my Core.php looks like this
<?php namespace Framework;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface as HttpKernelInterface;
class Core implements HttpKernelInterface
{
protected $routes = array();
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
{
$path = $request->getPathInfo();
// Does this URL match a route?
if (array_key_exists($path, $this->routes)) {
// execute the callback
$controller = $this->routes[$path];
$response = $controller();
} else {
// no route matched, this is a not found.
$response = new Response('Not found!', Response::HTTP_NOT_FOUND);
}
return $response;
}
// Associates an URL with a callback function
public function map($path, $controller) {
$this->routes[$path] = $controller;
}
}
Does anyone know what the bug is? What did I screw up?
As I mentioned in the comments, you're just missing the last little detail (->send()) where you tell the Response object to send the headers and echo the content. So:
$response = $app->handle($request);
$response->send();
And I think that should do it!
Cheers!
Related
I've been testing the new Slim 4 framework and redirects work fine for me in normal classes, but I cannot seem to get them working in middleware, where a response is dynamically generated (apparently?) by the Request Handler. When I try to redirect with a Location header, it simply fails to redirect, and my route continues to the original location.
Here’s a basic version of my authentication middleware for testing:
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class AuthMiddleware extends Middleware {
public function __invoke(Request $request, RequestHandler $handler): Response {
$response = $handler->handle($request);
$loggedInTest = false;
if ($loggedInTest) {
echo "User authorized.";
return $response;
} else {
echo "User NOT authorized.";
return $response->withHeader('Location', '/users/login')->withStatus(302);
}
}
}
Has anybody got this to work? And if so, how did you accomplish it? Thanks in advance.
I think I see the problem with this code.
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class AuthMiddleware extends Middleware {
public function __invoke(Request $request, RequestHandler $handler): Response {
$response = $handler->handle($request);
$loggedInTest = false;
if ($loggedInTest) {
echo "User authorized.";
return $response;
} else {
echo "User NOT authorized.";
return $response->withHeader('Location', '/users/login')->withStatus(302);
}
}
}
When you call $handler->handle($request), that processes the request normally and calls whatever closure is supposed to handle the route. The response hasn't been completed yet, you can still append stuff to it, but the headers are already set, so you can't do a redirect, because the headers are done.
Maybe try this:
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response;
class AuthMiddleware extends Middleware {
public function __invoke(Request $request, RequestHandler $handler): ResponseInterface {
$loggedInTest = false;
if ($loggedInTest) {
$response = $handler->handle($request);
echo "User authorized.";
return $response;
} else {
$response = new Response();
// echo "User NOT authorized.";
return $response->withHeader('Location', '/users/login')->withStatus(302);
}
}
}
If the login test fails, we never call $handler->handle(), so the normal response doesn't get generated. Meanwhile, we create a new response.
Note that the ResponseInterface and Response can't both be called Response in the same file, so I had to remove that alias, and just call the ResponseInterface by its true name. You could give it a different alias, but I think that would only create more confusion.
Also, I commented out the echo before the redirect. I think this echo will force headers to be sent automatically, which will break the redirect. Unless Slim 4 is doing output buffering, in which case you're still not going to see it, because the redirect will immediately send you to a different page. Anyway, I commented it out to give the code the best chance of working but left it in place for reference.
Anyway, I think if you make that little change, everything will work. Of course, this post is almost a year old, so you've probably solved this on your own, switched to F3, or abandoned the project by now. But hopefully, this will be helpful to someone else. That's the whole point of StackOverflow, right?
eimajenthat is right, except that you cannot create an instance of interface.
Try this instead:
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response;
class AuthMiddleware extends Middleware {
public function __invoke(Request $request, RequestHandler $handler): Response {
global $app; // Assuming $app is your global object
$loggedInTest = false;
if ($loggedInTest) {
$response = $handler->handle($request);
echo "User authorized.";
return $response;
} else {
$response = $app->getResponseFactory()->createResponse();
// echo "User NOT authorized.";
return $response->withHeader('Location', '/users/login')->withStatus(302);
}
}
}
I was growing so frustrated by Slim 4 and redirect issues that I took a look at FatFreeFramework and had the exact same problem. So I knew it was something I was doing. My code was putting the app into a never-ending redirect loop. I can make it work by validating the redirect URL like so in FatFreeFramework:
class Controller {
protected $f3;
public function __construct() {
$isLoggedIn = false;
$this->f3 = Base::instance();
if ($isLoggedIn == false && $_SERVER['REQUEST_URI'] != '/login') {
$this->f3->reroute('/login');
exit();
}
}
}
Therefore, although I haven't actually taken the time to test it, I'm assuming I could fix it in Slim 4 by doing something like:
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class AuthMiddleware extends Middleware {
public function __invoke(Request $request, RequestHandler $handler): Response {
$response = $handler->handle($request);
$loggedInTest = false;
if (!$loggedInTest && $_SERVER['REQUEST_URI'] != '/user/login') {
return return $response->withHeader('Location', '/users/login')->withStatus(302);
} else {
return $response;
}
}
}
Does anybody have another idea for how to break a continuous redirect loop? Or is the $_SERVER variable the best option?
Thanks in advance.
Use 2 response
namespace App\middleware;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response as Response7;
use Psr\Http\Message\ResponseInterface as Response;
final class OtorisasiAdmin {
public function __invoke(Request $request, RequestHandler $handler): Response {
$session = new \Classes\session();
$session->start();
$isAdmin=($session->has("login","admin"))?true:false;
if(!$isAdmin){
$response = new Response7();
$error = file_get_contents(__dir__."/../../src/error/404.html");
$response->getBody()->write($error);
return $response->withStatus(404);
}
$response=$handler->handle($request);
return $response;
}
}
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/
I am trying to setup a project for an API using slim framework version 3, I don't know who made the PSR-7 and marked the response object as immutable, I don't see any use in that (IMHO. please explain me if I am wrong). Things were much easier when it was slim 2. Now I came back to slim after a long time.
I have a route which is a post method, I am getting data and saving it to the database and I am trying to send 201 as the response code. all the examples and the documentation is showing you how can you change the response code within the index.php file itself, But I am trying to change it from a response builder which I have tried to use the factory pattern to provide different responses. The problem is the response code always stays 200 no matter what function I call from the response builder class. I tried many forums and different ways of slim but still couldn't able to pull this up. I almost decided to give up on a PSR 7 router implementation and going to implement my own routing solution. But I remember not to reinvent the wheel again so I came here as a final try. Below is the code.
the route definition
$app->post('/users', function(ServerRequestInterface $req, ResponseInterface $res) {
$data = $req->getParsedBody();
$model = new \Apex\Models\User(ApexDB::getInstance());
$jsonBuilder = ApexResponse::getBuilder('JSON', $res);
$control = new \Apex\Controllers\User($model, $jsonBuilder);
$control->create($data);
});
the controller method (abstract I am just setting it up)
public function create($data) {
if($this->model->save($data)) {
$this->response->build($data,201);
} else {
$this->response->build('error',400);
}
}
the JSON builder
class JSONBuilder implements Response
{
public $response;
public function __construct($response)
{
$this->response = $response;
}
public function build($data, $status)
{
$response = $this->response->withJSON($data,$status);
return $response;
}
}
can anyone point me in the right direction?
The PSR-7 decision to use immutable objects for Request and Response is documented in the Why value objects? section of the Meta document.
With Slim 3, you must always return a Response instance from the controller method.
$app->post('/users', function(ServerRequestInterface $req, ResponseInterface $res) {
$data = $req->getParsedBody();
$model = new \Apex\Models\User(ApexDB::getInstance());
$jsonBuilder = ApexResponse::getBuilder('JSON', $res);
$control = new \Apex\Controllers\User($model, $jsonBuilder);
return $control->create($data);
});
and then your create method also needs to return the $response:
public function create($data) {
if($this->model->save($data)) {
$this->response->build($data,201);
} else {
$this->response->build('error',400);
}
return $this->response;
}
It should then work.
However, you can use the controller method directly from the route declaration and avoid the need for a the closure:
$app->post('/users', `Apex\Controllers\User::create`);
The controller's create method would then look like this:
namespace Apex\Controllers;
class User
{
public function create($request, $response)
{
$data = $request->getParsedBody();
$model = new \Apex\Models\User(ApexDB::getInstance());
$jsonBuilder = ApexResponse::getBuilder('JSON', $response);
if ($model->save($data)) {
$response = $jsonBuilder->build($data, 201);
} else {
$response = $jsonBuilder->build('error', 400);
}
return $response;
}
}
Finally, consider rka-content-type-renderer instead of JsonBuilder, though maybe it does more than you've shown.
Update:
Ideally you'd use constructor injection to inject the User model into the controller. To do this:
Update your controller:
namespace Apex\Controllers;
use Apex\Models\User as UserModel;
class User
{
protected $userModel;
public function __construct(UserModel $userModel)
{
$this->userModel = $userModel;
}
public function create($request, $response)
{
$data = $request->getParsedBody();
$jsonBuilder = ApexResponse::getBuilder('JSON', $response);
if ($this->userModel->save($data)) {
$response = $jsonBuilder->build($data, 201);
} else {
$response = $jsonBuilder->build('error', 400);
}
return $response;
}
}
Write a factory for the Pimple dependency injection container:
$container = $app->getContainer();
$container['Apex\Controllers\User'] = function ($c) {
$userModel = new \Apex\Models\User(ApexDB::getInstance());
return new \ApexController\User($userModel);
};
I'm doing Acl in my Module.php and i'd like to redirect users to a specific route if they are unauthorized.
My route is Literal and is parameterless (i.e., has default controller and action params set in config).
I just can't find a way to do so.
Actually i know i can set controller and action params of my RouteMatch (i get it from the MvcEvent) but i want to set a new route by its name and without setting params.
Any help is appreciated, thanks
It depends on the function definition where you want to redirect from.
One could be a simple and direct onBootstrap -> on Dispatch, a function definition -
Eg:
use Zend\Mvc\MvcEvent;
.....
........
class Module {
public function onBootstrap(MvcEvent $e)
{
$e->getApplication()->getEventManager()->getSharedManager()->attach('SOME_CONTROLLER OR * (-> all controllers)', 'dispatch', function($e) {
$controller = $e->getTarget();
if (unauthorized_user) {
$controller->plugin('redirect')->toRoute('ROUTE_NAME');
}
}, 100);
}
......
........
}
Other could be onBootstrap -> on Route -
This gets tricky because the function gets executed even before the route is finalized and so you don't have a $e->getTarget() i.e. Controller object yet.
public function onBootstrap(MvcEvent $e) {
$e->getApplication()
->getEventManager()
->getSharedManager()
->attach('*', 'route', function($e) {
if (unauthorized_user) {
return $this->customRedirect($e, 'ROUTE_NAME');
} else {
return $this->customRedirect($e, 'ROUTE_NAME', 'ACTION_NAME');
}
}, 100);
}
public function customRedirect($event, $route, $action = '') {
$url = $event->getRouter()->assemble(array('action' => $action), array('name' => $route));
$response = $event->getResponse();
$response->getHeaders()->addHeaderLine('Location', $url);
$response->setStatusCode(302);
$response->sendHeaders();
exit();
}
Just in-case to get the current route_name, controller_name and action_name.
Can use this -
$sm = $e->getApplication()->getServiceManager();
$router = $sm->get('router');
$request = $sm->get('request');
$matchedRoute = $router->match($request);
$params = $matchedRoute->getParams();
$route_name = $matchedRoute->getMatchedRouteName();
$controller_name = $params['controller'];
$action_name = $params['action'];
I hope it helps someone.
I am just starting a new Silex project. I am using the Cartalyst Sentry Authentication package and I wish to inject into my controller Service Controllers. Here is my attempt at using Silex's built in dependency container which extends Pimple. I would just like some feedback on whether I am going about things the right way and what I can improve.
$app['sentry'] = $app->share(function() use ($app) {
$hasher = new Cartalyst\Sentry\Hashing\NativeHasher;
$userProvider = new Cartalyst\Sentry\Users\Eloquent\Provider($hasher);
$groupProvider = new Cartalyst\Sentry\Groups\Eloquent\Provider;
$throttleProvider = new Cartalyst\Sentry\Throttling\Eloquent\Provider($userProvider);
$session = new Cartalyst\Sentry\Sessions\NativeSession;
$cookie = new Cartalyst\Sentry\Cookies\NativeCookie(array());
$sentry = new Cartalyst\Sentry\Sentry(
$userProvider,
$groupProvider,
$throttleProvider,
$session,
$cookie
);
Cartalyst\Sentry\Facades\Native\Sentry::setupDatabaseResolver(new PDO(
$app['db.dsn'],
$app['db.options']['user'],
$app['db.options']['password']
));
return $sentry;
});
Defining my controller:
// General Service Provder for Controllers
$app->register(new Silex\Provider\ServiceControllerServiceProvider());
$app['user.controller'] = $app->share(function() use ($app) {
return new MyNS\UserController($app);
});
$app->get('/user', "user.controller:indexAction");
Here is my controller, note that app['sentry'] is available to my controller by injecting it into the constructor.
class UserController
{
private $app;
public function __construct(Application $app)
{
$this->app = $app;
}
public function indexAction()
{
// just testing various things here....
$user = $this->app['sentry']->getUserProvider()->findById(1);
$sql = "SELECT * FROM genes";
$gene = $this->app['db']->fetchAssoc($sql);
$this->app['monolog']->addDebug(print_r($gene,true));
return new JsonResponse($user);
}
}
This is the process I go through after finding a vendor package I'd like to use. I'm using a simpler library in order to focus on setting up the service provider.
Install the new package via composer.
~$ composer require ramsey/uuid
Create the service provider.
<?php
namespace My\Namespaced\Provider;
use Silex\Application;
use Silex\ServiceProviderInterface;
use Rhumsaa\Uuid\Uuid;
class UuidServiceProvider implements ServiceProviderInterface
{
public function register(Application $app)
{
$app['uuid1'] = $app->share(function () use ($app) {
$uuid1 = Uuid::uuid1();
return $uuid1;
});
$app['uuid4'] = $app->share(function () use ($app) {
$uuid4 = Uuid::uuid4();
return $uuid4;
});
}
public function boot(Application $app)
{
}
}
Register the service provider.
$app->register(new My\Namespaced\Provider\UuidServiceProvider());
Use the new service in a controller.
<?php
namespace My\Namespaced\Controller;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class ExampleController
{
public function indexAction(Application $app, Request $request)
{
$uuid = $app['uuid4']->toString();
return new Response('<h2>'.$uuid.'</h2>');
}
}