PHPackages                             shoot/shoot - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Templating &amp; Views](/categories/templating)
4. /
5. shoot/shoot

ActiveLibrary[Templating &amp; Views](/categories/templating)

shoot/shoot
===========

Shoot aims to make providing data to your templates more manageable

v3.3.0(4y ago)40229.9k142MITPHPPHP ^7.2 || ^8.0CI failing

Since Dec 12Pushed 2y ago2 watchersCompare

[ Source](https://github.com/shootphp/shoot)[ Packagist](https://packagist.org/packages/shoot/shoot)[ RSS](/packages/shoot-shoot/feed)WikiDiscussions master Synced 3d ago

READMEChangelog (10)Dependencies (6)Versions (13)Used By (2)

Shoot
=====

[](#shoot)

[![License](https://camo.githubusercontent.com/55c0218c8f8009f06ad4ddae837ddd05301481fcf0dff8e0ed9dadda8780713e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE.md)[![Coverage](https://camo.githubusercontent.com/3f7f0a597f589ffc728e0455302b0d035549806d6bc152b850d7407627613dd0/68747470733a2f2f696d672e736869656c64732e696f2f7363727574696e697a65722f636f7665726167652f672f73686f6f747068702f73686f6f742e7376673f7374796c653d666c61742d737175617265)](https://scrutinizer-ci.com/g/shootphp/shoot/code-structure)[![Code quality](https://camo.githubusercontent.com/3bfdaf01a6a15238eddcf8fb3263bd2f5f3b01e630be076600bec5aa1dc29c9c/68747470733a2f2f696d672e736869656c64732e696f2f7363727574696e697a65722f672f73686f6f747068702f73686f6f742e7376673f7374796c653d666c61742d737175617265)](https://scrutinizer-ci.com/g/shootphp/shoot)

Shoot is an extension for [Twig](https://twig.symfony.com/), 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
-------------

[](#prerequisites)

Shoot assumes you're using PHP 7.2 and Twig to render templates in a [PSR-7](https://www.php-fig.org/psr/psr-7/) HTTP context. It also needs a [PSR-11](https://www.php-fig.org/psr/psr-11/) compatible DI container.

Although not a requirement, a framework with support for [PSR-15](https://www.php-fig.org/psr/psr-15/) HTTP middleware does make your life a little easier.

What it does
------------

[](#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
------------

[](#installation)

Shoot is available through [Packagist](https://packagist.org/packages/shoot/shoot). Simply install it with Composer:

```
$ composer require shoot/shoot
```

Getting started
---------------

[](#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

[](#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

[](#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](https://twig.symfony.com/doc/2.x/coding_standards.html), 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

[](#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' %}

        {{ title }}

        {{ title }}
        Written by {{ author_name }}
        {{ body }}

```

### Presenters

[](#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
---------

[](#changelog)

Please see the [changelog](CHANGELOG.md) for more information on what has changed recently.

Testing
-------

[](#testing)

```
$ composer run-script test
```

License
-------

[](#license)

The MIT License (MIT). Please see the [license file](LICENSE.md) for more information.

###  Health Score

42

—

FairBetter than 90% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity39

Limited adoption so far

Community19

Small or concentrated contributor base

Maturity75

Established project with proven stability

 Bus Factor1

Top contributor holds 96.1% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~116 days

Recently: every ~193 days

Total

12

Last Release

1799d ago

Major Versions

v0.4.0 → v1.0.02018-08-27

v1.0.0 → v2.0.02019-03-04

v2.0.0 → v3.0.02019-05-01

PHP version history (3 changes)v0.1.0PHP ^7.0

v2.0.0PHP ^7.2

v3.3.0PHP ^7.2 || ^8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/b3c7b56b2b65e2ea9ef1a386ad07ee112518844fdfab4f2d76d42b20b44b1c90?d=identicon)[victorwelling](/maintainers/victorwelling)

---

Top Contributors

[![victorwelling](https://avatars.githubusercontent.com/u/2248300?v=4)](https://github.com/victorwelling "victorwelling (49 commits)")[![pcvandamcb](https://avatars.githubusercontent.com/u/11274202?v=4)](https://github.com/pcvandamcb "pcvandamcb (1 commits)")[![timoschinkel](https://avatars.githubusercontent.com/u/31616857?v=4)](https://github.com/timoschinkel "timoschinkel (1 commits)")

---

Tags

phpphp7shoottwigtwig-extensionmiddlewareplugintwigdatamodeltemplatingviewtemplatesextensionpresenterpresentationshoot

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/shoot-shoot/health.svg)

```
[![Health](https://phpackages.com/badges/shoot-shoot/health.svg)](https://phpackages.com/packages/shoot-shoot)
```

###  Alternatives

[laminas/laminas-view

Fast and type safe HTML templating library with a flexible plugin system supporting multistep template composition

7526.3M230](/packages/laminas-laminas-view)[rareloop/lumberjack-core

A powerful MVC framework for the modern WordPress developer. Write better, more expressive and easier to maintain code

42155.0k19](/packages/rareloop-lumberjack-core)[daycry/twig

twig for Codeigniter 4

145.1k2](/packages/daycry-twig)[deefour/presenter

Presenters/Decorators for PHP Objects

122.5k](/packages/deefour-presenter)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
