Giter Club home page Giter Club logo

shoot's Introduction

Shoot

License Coverage Code quality

Shoot is an extension for Twig, a popular template engine for PHP. Shoot aims to make providing data to your templates more manageable. Think of Shoot as a DI container for template data.

Prerequisites

Shoot assumes you're using PHP 7.2 and Twig to render templates in a PSR-7 HTTP context. It also needs a PSR-11 compatible DI container.

Although not a requirement, a framework with support for PSR-15 HTTP middleware does make your life a little easier.

What it does

Typically, you first load your data and then use Twig to render that data into HTML. Shoot turns that around. You start rendering your templates and Shoot loads the data as needed. Enjoy this ASCII illustration:

+---------------+          +---------------+
|    Request    |          |    Request    |
+-------+-------+          +-------+-------+
        |                          |     +---------+
        |                          |     |         |
+-------v-------+          +-------v-----v-+     +-+-------------+
|   Load data   |          |  Render view  +----->   Load data   |
+-------+-------+          +-------+-------+     +---------------+
        |                          |
        |                          |
+-------v-------+          +-------v-------+
|  Render view  |          |   Response    |
+-------+-------+          +---------------+
        |
        |
+-------v-------+
|   Response    |
+---------------+

For this to work, Shoot introduces a few concepts:

  • Presentation models – Think of them as data contracts for your templates, i.e. Views.
  • Presenters – These do the actual work. A presenter is coupled to a specific presentation model, and loads just the data it needs. These presenters are automatically invoked by Shoot as your templates are rendered.
  • Middleware – As each template is rendered, it passes through Shoot's middleware pipeline. Invoking the presenters is done by middleware, but there are plenty of other use cases, such as logging and debugging.

Installation

Shoot is available through Packagist. Simply install it with Composer:

$ composer require shoot/shoot

Getting started

First, set up the pipeline. All views being rendered by Twig pass through it, and are processed by Shoot's middleware. For Shoot to be useful, you'll need at least the PresenterMiddleware, which takes a DI container as its dependency.

All that's left is then to install Shoot in Twig:

$middleware = [new PresenterMiddleware($container)];
$pipeline = new Pipeline($middleware);
$installer = new Installer($pipeline);

$twig = $installer->install($twig);

With Shoot now set up, let's take a look at an example of how you can use it.

Request context

Before we're able to use Shoot's pipeline, it needs the current HTTP request being handled to provide context to its middleware and the presenters. You set it through the withRequest method, which accepts the request and a callback as its arguments. The callback is immediately executed and its result returned. During the execution of the callback, the request is available to the pipeline.

$result = $pipeline->withRequest($request, function () use ($twig): string {
    return $twig->render('template.twig');
});

In the example above, result will contain the rendered HTML as returned by Twig.

To avoid having to manually set the request on the pipeline everywhere you render a template, it's recommended to handle this in your HTTP middleware. This way, it's always taken care of. Shoot comes with PSR-15 compatible middleware to do just that: Shoot\Shoot\Http\ShootMiddleware.

Presentation models

Now with the plumbing out of the way, it's time to create our first presentation model. We'll use a blog post for our example:

namespace Blog;

final class Post extends PresentationModel implements HasPresenterInterface
{
    protected $author_name = '';
    protected $author_url = '';
    protected $body = '';
    protected $title = '';

    public function getPresenterName(): string
    {
        return PostPresenter::class;
    }
}

The fields in a presentation model are the variables that'll be assigned to your template. That's why, as per Twig's coding standards, they use snake_case. These fields must be protected.

This presentation model implements the HasPresenterInterface. This indicates to Shoot that there's a presenter available to load the data of this model. This interface defines the getPresenterName method. This method should return the name through which the presenter can be resolved by your DI container.

Templates

To assign the model to our template, we use Shoot's model tag. Set it at the top of your template and reference the class name of your model:

{% model 'Blog\\Post' %}
<!doctype html>
<html>
    <head>
        <title>{{ title }}</title>
    </head>
    <body>
        <h1>{{ title }}</h1>
        <p>Written by <a href="{{ author_url }}">{{ author_name }}</a></p>
        <p>{{ body }}</p>
    </body>
</html>

Presenters

With the presentation model defined and assigned to the template, we can now focus on writing the presenter. Since presenters are retrieved from your DI container, you can easily inject any dependencies needed to load your data. In the following example, we need a database and router:

namespace Blog;

final class PostPresenter implements PresenterInterface
{
    private $database;
    private $router;

    public function __construct(PDO $database, Router $router)
    {
        $this->database = $database;
        $this->router = $router;
    }

    public function present(ServerRequestInterface $request, PresentationModel $presentationModel): PresentationModel
    {
        // post_id could be a variable in our route, e.g. /posts/{post_id}
        $postId = $request->getAttribute('post_id', '');

        $post = $this->fetchPost($postId);

        return $presentationModel->withVariables([
            'author_name' => $post['author_name'],
            'author_url' => $this->router->pathFor('author', $post['author_id']),
            'body' => $post['body'],
            'title' => $post['title']
        ]);
    }

    private function fetchPost(string $postId): array
    {
        // Fetches the post from the database
    }
}

Whenever the template is rendered, the presenter's present method will be called by Shoot with the current request and the presentation model assigned to the template.

It will fetch the necessary data from the database, look up the correct route to the author's profile and return the presentation model updated with its variables set. Shoot then assigns these variables to the template, and Twig takes care of rendering it. Job done!

Changelog

Please see the changelog for more information on what has changed recently.

Testing

$ composer run-script test

License

The MIT License (MIT). Please see the license file for more information.

shoot's People

Contributors

intrepidity avatar pcvandamcb avatar timoschinkel avatar victorwelling avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

shoot's Issues

Tag new release

Was trying to configure Shoot according to the README, only to find out the Installer isn't available yet in the latest release.

Could you tag a new release? :)

Support for PHP 8

The PHP constraint of the library is set to ^7.2. This means that PHP 8 is not supported.

I ran Composer and the test suite on PHP 8 and all tests seem to pass:

docker run -it -v $(pwd):/var/www/app php:8.0 /bin/bash -c "cd /var/www/app && curl -sS https://getcomposer.org/installer | php && php composer.phar install && ./vendor/bin/phpunit"

A draft pull request is available in #24

I wanted to update the CI inspections as well, but I think these are not configured in the codebase itself. After creating the draft pull request I noticed the inspections are only run on PHP 7.2. Which is why the pull request is in draft state.

Does it make sense to also look into moving the CI inspections to Github Actions and add a matrix of PHP versions to be tested? From what I can see this will have the downside of not having the Scrutinizer inspections and not having the badges shown in the README right now. The Scrutinizer inspections can maybe be replaced with Psalm. If interested I'm willing to create a pull request for this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.