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

Conversation

Projects
None yet
6 participants
@l0gicgate
Copy link
Contributor

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link
Contributor Author

commented Nov 1, 2018

@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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link
Member

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

This comment has been minimized.

Copy link
Contributor

commented Nov 2, 2018

Can you also include an example using Guzzle/psr7?

@akrabat akrabat added the Slim 4 label Nov 24, 2018

@akrabat

This comment has been minimized.

Copy link
Member

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

This comment has been minimized.

Copy link
Member

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.

// Add route
$route = $this->createRoute($methods, $pattern, $handler);
/** @var callable $routeHandler */
$routeHandler = $handler;

This comment has been minimized.

Copy link
@akrabat

akrabat Nov 24, 2018

Member

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

This comment has been minimized.

Copy link
@l0gicgate

l0gicgate Nov 24, 2018

Author Contributor

Because PHPStan complains.

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

This comment has been minimized.

Copy link
@akrabat

akrabat Nov 24, 2018

Member

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

{
HeaderStackTestAsset::reset();
}
public static function setupBeforeClass()
{
// ini_set('log_errors', 0);

This comment has been minimized.

Copy link
@akrabat

akrabat Nov 24, 2018

Member

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);

This comment has been minimized.

Copy link
@akrabat

akrabat Nov 25, 2018

Member

run() no longer takes a ResponseFactory.

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

This comment has been minimized.

Copy link
@akrabat

akrabat Nov 25, 2018

Member

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

This comment has been minimized.

Copy link
@akrabat

akrabat Nov 25, 2018

Member

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

This comment has been minimized.

Copy link
@akrabat

akrabat Nov 25, 2018

Member

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

1 of 2 checks passed

coverage/coveralls Coverage decreased (-0.6%) to 97.389%
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details

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 l0gicgate:PSR7-Decoupling branch Mar 2, 2019

@l0gicgate l0gicgate referenced this pull request Apr 25, 2019

Merged

Slim 4 Alpha Release #2665

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.