Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PSR-7 Implementation Decoupling #2529

Merged
merged 48 commits into from
Nov 25, 2018
Merged

Conversation

l0gicgate
Copy link
Member

@l0gicgate l0gicgate commented Nov 1, 2018

This PR decouples the App and all the out-of-the-box Slim middlewares entirely from any specific PSR-7 Implementation. You are now able to use any implementation of your choice. I also extracted the App::respond() and App::finalize() methods and created a ResponseEmitter.

This also removes the dependency on Slim/Http which has become a PSR-7 Object Decorator repository since version 0.5. You are free to use the decorators if you would like since they piggy back on top of the PSR-17 factories that are usually provided by the PSR-7 Implementation of your choice.

Choose a PSR-7 Implementation

Before you can get up and running with Slim you will need to choose a PSR-7 implementation that best fits your application. A few notable ones:

  • Nyholm/psr7 - This is the fastest, strictest and most lightweight implementation at the moment
  • Guzzle/psr7 - This is the implementation used by the Guzzle Client. It is not as strict but adds some nice functionality for Streams and file handling. It is the second fastest implementation but is a bit bulkier
  • zend-diactoros - This is the Zend implementation. It is the slowest implementation of the 3.

Example Usage With Nyholm/psr7 and Nyholm/psr7-server

<?php
require 'vendor/autoload.php';

use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7Server\ServerRequestCreator;

/**
 * We need to instantiate our factories before instantiating Slim\App
 * In the case of Nyholm/psr7 the Psr17Factory provides all the Http-Factories in one class
 * which includes ResponseFactoryInterface
 */
$psr17Factory = new Psr17Factory();
$serverRequestFactory = new ServerRequestCreator(
    $psr17Factory,
    $psr17Factory,
    $psr17Factory,
    $psr17Factory
);

/**
 * The App::__constructor() method takes 1 mandatory parameter and 2 optional parameters
 * @param ResponseFactoryInterface Any implementation of a ResponseFactory
 * @param ContainerInterface|null Any implementation of a Container
 * @param array Settings array
 */
$app = new Slim\App($psr17Factory);
$app->get('/hello/{name}', function ($request, $response, $args) {
    return $response->getBody()->write("Hello, " . $args['name']);
});

/**
 * The App::run() method takes 1 parameter
 * @param ServerRequestInterface An instantiation of a ServerRequest
 */
$request = $serverRequestFactory->fromGlobals();
$app->run($request);

Example Usage With Zend Diactoros & Zend HttpHandleRunner Response Emitter

<?php
require 'vendor/autoload.php';

use Zend\Diactoros\ResponseFactory;
use Zend\Diactoros\ServerRequestFactory;
use Zend\HttpHandlerRunner\Emitter\SapiEmitter;

$responseFactory = new ResponseFactory();
$serverRequestFactory = new ServerRequestFactory();

/**
 * The App::__constructor() method takes 1 mandatory parameter and 2 optional parameters
 * @param ResponseFactoryInterface Any implementation of a ResponseFactory
 * @param ContainerInterface|null Any implementation of a Container
 * @param array Settings array
 */
$app = new Slim\App($responseFactory);
$app->get('/hello/{name}', function ($request, $response, $args) {
    return $response->getBody()->write("Hello, " . $args['name']);
});

/**
 * The App::handle() method takes 1 parameter
 * Note we are using handle() and not run() since we want to emit the response using Zend's Response Emitter
 * @param ServerRequestInterface An instantiation of a ServerRequest
 */
$request = ServerRequestFactory::fromGlobals();
$response = $app->handle($request);

/**
 * Once you have obtained the ResponseInterface from App::handle()
 * You will need to emit the response by using an emitter of your choice
 * We will use Zend HttpHandleRunner SapiEmitter for this example
 */
$responseEmitter = new SapiEmitter();
$responseEmitter->emit($response);

Example Usage With Slim-Http Decorators and Zend Diactoros

<?php
require 'vendor/autoload.php';

use Slim\Http\Factory\DecoratedResponseFactory;
use Slim\Http\Decorators\ServerRequestDecorator;
use Zend\Diactoros\ResponseFactory;
use Zend\Diactoros\ServerRequestFactory;
use Zend\Diactoros\StreamFactory;

$responseFactory = new ResponseFactory();
$streamFactory = new StreamFactory();
$decoratedResponseFactory = new DecoratedResponseFactory($responseFactory, $streamFactory);
$serverRequestFactory = new ServerRequestFactory();

/**
 * The App::__constructor() method takes 1 mandatory parameter and 2 optional parameters
 * Note that we pass in the decorated response factory which will give us access to the Slim\Http
 * decorated Response methods like withJson()
 * @param ResponseFactoryInterface Any implementation of a ResponseFactory
 * @param ContainerInterface|null Any implementation of a Container
 * @param array Settings array
 */
$app = new Slim\App($decoratedResponseFactory);
$app->get('/hello/{name}', function ($request, $response, $args) {
    return $response->withJson(['Hello' => 'World']);
});

/**
 * The App::run() method takes 1 parameter
 * Note that we pass in the decorated server request object which will give us access to the Slim\Http
 * decorated ServerRequest methods like withRedirect()
 * @param ServerRequestInterface An instantiation of a ServerRequest
 */
$request = ServerRequestFactory::fromGlobals();
$decoratedServerRequest = new ServerRequestDecorator($request);
$app->run($decoratedServerRequest);

Example Usage With Guzzle PSR-7 and Guzzle HTTP Factory

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Psr7\ServerRequest;
use Http\Factory\Guzzle\ResponseFactory;

$responseFactory = new ResponseFactory();

/**
 * The App::__constructor() method takes 1 mandatory parameter and 2 optional parameters
 * @param ResponseFactoryInterface Any implementation of a ResponseFactory
 * @param ContainerInterface|null Any implementation of a Container
 * @param array Settings array
 */
$app = new Slim\App($responseFactory);
$app->get('/hello/{name}', function ($request, $response, $args) {
    return $response->getBody()->write("Hello, " . $args['name']);
});

/**
 * The App::run() method takes 1 parameter
 * @param ServerRequestInterface An instantiation of a ServerRequest
 */
$request = ServerRequest::fromGlobals();
$app->run($request);

Status

  • Code Review & Discussion

@odan
Copy link
Contributor

odan commented Nov 1, 2018

Thank you very much for this great feature.

My question is: Will Slim 4 be delivered with a "default" PSR-7 implementation or do we have to make this decision ourselves now?

@l0gicgate
Copy link
Member Author

@odan you have to make the decision yourself as to which PSR-7 implementation you choose. However Slim-Skeleton will ship with Nyholm/psr-7 and Slim-Http decorators.

@odan
Copy link
Contributor

odan commented Nov 1, 2018

@l0gicgate I am surprised that the Slim "inhouse" PSR-7 implementation slimphp/Slim-Http is not listed. What reason is there for not using slim/http? What does this mean for the future of Slim-Http library? Is it possible to use use the slim/http library in Slim 4 or is it not recommended?

@akrabat
Copy link
Member

akrabat commented Nov 1, 2018

@odan I think you may mean Slim-Psr7?

If someone gets that into shape so that it passes the PSR-7 tests, the there's no reason why it can't be included. As it it, it's not as good as the alternatives.

Slim-Http is a set of decorators and I think we'll encourage their use with Slim4.

@theodorejb
Copy link
Contributor

Can you also include an example using Guzzle/psr7?

@akrabat
Copy link
Member

akrabat commented Nov 24, 2018

While, I very much like the separation created by ResponseEmitter, I would like it used by default in run(). If the user doesn't want to use our emitter, then they should use handle().

@akrabat
Copy link
Member

akrabat commented Nov 24, 2018

It would be convenient if CallableResolver was aware of the ResponseFactory, so that it could pass it into the constructor of the classes it creates as the second parameter. This would make life much easier for creating actions and middleware that return Response objects without using a container.

Slim/Router.php Outdated
// Add route
$route = $this->createRoute($methods, $pattern, $handler);
/** @var callable $routeHandler */
$routeHandler = $handler;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the point of renaming the variable by assignment? Surely we should just rename it in the method parameter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because PHPStan complains.

parameters:
level: max
ignoreErrors:
- '^Parameter #1 $callable of callable Slim\\Interfaces\\InvocationStrategyInterface expects callable, array|callable given.^'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a comments to point out where this one happens?

{
HeaderStackTestAsset::reset();
}

public static function setupBeforeClass()
{
// ini_set('log_errors', 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need this commented out line or the one in tearDownAfterClass()

README.md Outdated
$responseEmitter = new ResponseEmitter();
$responseEmitter->emit($response);
$request = $serverRequestFactory->fromGlobals();
$app->run($request, $psr17Factory);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

run() no longer takes a ResponseFactory.

$app->get('/hello/{name}', function ($request, $response, $args) {
return $response->getBody()->write("Hello, " . $args['name']);
});

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lose this blank line.

README.md Outdated
* which include ResponseFactoryInterface
* @param ServerRequestInterface An instantiation of a ServerRequest
* @param ResponseFactoryInterface An instantiation of a ResponseFactory
* The App::__constructor() Method takes 1 mandatory parameter and 2 optional parameters
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method has a lowercase m unless starting a sentence.

README.md Outdated
* You will need to emit the response by using an emitter of your choice
* We will use Slim ResponseEmitter for this example
* But you could use Zend HttpHandleRunner SapiEmitter or other
* The App::run() Method takes 1 parameters
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/parameters/parameter

@akrabat akrabat added this to the 4.0 milestone Nov 25, 2018
@akrabat akrabat merged commit a62881b into slimphp:4.x Nov 25, 2018
akrabat added a commit that referenced this pull request Nov 25, 2018
akrabat added a commit that referenced this pull request Nov 25, 2018
@l0gicgate l0gicgate deleted the PSR7-Decoupling branch March 2, 2019 15:52
@l0gicgate l0gicgate mentioned this pull request Apr 25, 2019
@l0gicgate l0gicgate mentioned this pull request Aug 1, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants