PHPackages                             bbc/ipr-resolver - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. bbc/ipr-resolver

AbandonedArchivedLibrary[Utility &amp; Helpers](/categories/utility)

bbc/ipr-resolver
================

A dependency resolution library.

v0.2.1(9y ago)02.0k1MITPHPPHP &gt;=5.5

Since Aug 5Pushed 9y ago52 watchersCompare

[ Source](https://github.com/bbc/ipr-php-resolver)[ Packagist](https://packagist.org/packages/bbc/ipr-resolver)[ RSS](/packages/bbc-ipr-resolver/feed)WikiDiscussions master Synced 4w ago

READMEChangelog (3)Dependencies (2)Versions (6)Used By (1)

BBC\\iPlayerRadio\\Resolver
===========================

[](#bbciplayerradioresolver)

A simple requirement resolution system for plain-old-php objects.

[![Build Status](https://camo.githubusercontent.com/6ecb2fd97e7f507cd3deebdd389c52b4c097d6396036fbd6760acf3f00bec73c/68747470733a2f2f7472617669732d63692e6f72672f6262632f6970722d7068702d7265736f6c7665722e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/bbc/ipr-php-resolver)[![Latest Stable Version](https://camo.githubusercontent.com/5bc77b4e272f735590e650ca59d1b52f5f943e3e39af6492dc63f69788b74fbf/68747470733a2f2f706f7365722e707567782e6f72672f6262632f6970722d7265736f6c7665722f762f737461626c652e737667)](https://packagist.org/packages/bbc/ipr-resolver)[![Total Downloads](https://camo.githubusercontent.com/cfd31a077237802a5b4a95591bb5346ae1673d1fde086c8210606722437507fe/68747470733a2f2f706f7365722e707567782e6f72672f6262632f6970722d7265736f6c7665722f646f776e6c6f6164732e737667)](https://packagist.org/packages/bbc/ipr-resolver)[![License](https://camo.githubusercontent.com/9a5e35c67c8450bf25893a961293536d07185bc071265d59bcd4af153d08e086/68747470733a2f2f706f7365722e707567782e6f72672f6262632f6970722d7265736f6c7665722f6c6963656e73652e737667)](https://packagist.org/packages/bbc/ipr-resolver)

- [Installation](#installation)
- [Background](#background)
- [Usage](#usage)
- [How this actually works](#how-this-actually-works)
- [Credits](#credits)

Installation
------------

[](#installation)

This is a standard composer library, so usual install rules apply:

```
$ composer require bbc/ipr-resolver
```

This library requires **PHP 5.5 or higher** as it makes use of [generators](http://php.net/manual/en/language.generators.overview.php)!

Background
----------

[](#background)

Objects often have dependencies on each other. It's a pain. To use a uniquely radio example; we sometimes put "Brand" objects on our pages (think "The Archers"). However, we have decided that the most useful thing to show to the audience is the latest episode from that brand. That doesn't come as part of the brand metadata, so we have to do a separate call for it. We end up with something like this:

```
$brand = $this->fetchBrand('The Archers');
$brand->loadLatestEpisode();

$twig->renderBrand($brand);
```

This gets worse if you have a list of brands and need to loop through each of them, hydrating them with their latest episode since the work is now probably happening in series and definitely cluttering up your code:

```
foreach ($brands as $brand) {
    // do this 50 times.
    $brand->loadLatestEpisode();
}
```

And what about if the latest episode ALSO has to make a data call to make itself whole! This is a nightmare. And how do I even inject dependencies down that chain?!

Wouldn't it be nice if we could just make a brand object, and say; "Sort yourself out bucko!" and it does. Yeah, we thought so too.

Enter stage left; the Resolver.

Usage
-----

[](#usage)

To flag that an object has dependencies, you need to implement the `BBC\iPlayerRadio\Resolver\HasRequirements` interface which has a single method: `requires()`.

```
use BBC\iPlayerRadio\Resolver\HasRequirements;

class Brand implements HasRequirements
{
    ...
    public function requires(array $flags = [])
    {
        $episodes = (yield new LatestEpisodesForProgramme($this->getPID(), 3));
        $this->latestEpisodes = ($episodes)? $episodes : [];
    }
    ...
}
```

If you've never come across [coroutines](https://wiki.php.net/rfc/generators#sending_values) before, this probably looks a bit weird! But you can think of it like a lazy promise, the yield throws up to the Resolver saying "I need this to continue, handle it!".

This is how you then grab a fully fleshed out item:

```
use BBC\iPlayerRadio\Resolver\Resolver;

$resolver = new Resolver();
$resolver->addBackend(new EpisodesBackend);

$brand1 = new Brand(['id' => 'p865ddf6']);
$brand2 = new Brand(['id' => 'pabcdefs']);

$resolver->resolve([$brand1, $brand2]);

// $brand1 and $brand2 are now fully hydrated with their latest episodes in the  $this->latestEpisodes variable.
```

Hang on, what's that `new EpisodesBackend`? This is how the Resolver knows how to solve requirements. Resolver backends take requirements and generate the actual result of the requirement.

So the EpisodesBackend would look something like this:

```
class EpisodesBackend implements BBC\iPlayerRadio\Resolver\ResolverBackend
{
    /**
     * Returns whether this backend can handle a given Requirement. Requirements
     * can be absolutely anything, so make sure to verify correctly against it.
     *
     * @param   mixed   $requirement
     * @return  bool
     */
    public function canResolve($requirement)
    {
        return $requirement instanceof LatestEpisodesForProgramme;
    }

    /**
     * Given a list of requirements, perform their resolutions. Requirements can
     * be absolutely anything from strings to full-bore objects.
     *
     * @param   array   $requirements
     * @return  array
     */
    public function doResolve(array $requirements)
    {
        $results = [];
        foreach ($requirements as $req) {
            if ($req instanceof LatestEpisodesForProgramme) {
                $results = $this->fetchEpisodesForProgramme($req->id, $req->limit);
            }
        }
        return $results;
    }
}
```

Whilst the syntax of using the resolver is slightly nicer, how does this help performance? We're still looping through and running each query synchronously.

Well, consider that the fetchLatestEpisodes was making a cURL request. We could now batch all those up do it as a single multi-curl request:

```
public function doResolve(array $requirements)
{
    $results = [];
    $urls = [];
    foreach ($requirements as $req) {
        if ($req instanceof LatestEpisodesForProgramme) {
            $urls[] = $req->getDataURL();
        }
    }

    // Now kick off a multi-curl request for all those URLs:
    $ch = curl_multi_init();
    ...

    return $results;
}
```

Using the Resolver allows you to ignore the actual details of how your objects fetch their data, and instead simply define what they *need* and let the Resolver and ResolverBackends do the work!

> **Hint hint** imagine coupling this with the [WebserviceKit](http://github.com/bbc/ipr-php-webservicekit) library with a ResolverBackend that looks for QueryInterface instances and then multiFetch()'s them... ;)

You can also pass flags into the Resolver to help `requires()` functions work out what they need to do:

```
class Brand implements HasRequirements
{
    public function requires(array $flags = [])
    {
        if (in_array('WITH_ATTRIBUTION', $flags)) {
            $attribution = (yield new WithAttribution($this->parent));
        }
    }
}
```

```
$resolver->resolve($brand, ['WITH_ATTRIBUTION']);
```

**Note**: all `requires()` functions see all flags, so keep them specific to avoid problems!

If a requirement is not supported by any backend (none of the `canResolve()` functions return true), then a `BBC\iPlayerRadio\Resolver\UnresolvableRequirementException` will be thrown, contained within it the requirement that failed, which you can access with `getFailedRequirement()`.

Resolver Backends
-----------------

[](#resolver-backends)

Resolver Backends have two functions; firstly to state whether or not they understand a requirement (via the `canResolve`function) and then to take a list of requirements that it can resolve and to resolve them (via `doResolve`)!

There are some rules for writing your own resolver backends:

- Be as specific and safe in `canResolve` as you can. Be sure that you can actually resolve it
- Keep backends generic. Have a "CURLResolverBackend" rather than individual "EpisodeResolver", "BrandResolver" etc. Resolver backends should be "resolution strategy" based rather than data model based. The more generic the better.
- The requirements passed in `doRequire` can be in any order and from multiple objects. Treat them that way!
- **Always** return the results in the same order as the requirements! Weird stuff will happen if you don't!

How this actually works
-----------------------

[](#how-this-actually-works)

You might be wondering how the whole `yield` thing works. It's a feature in PHP called [Generators](http://php.net/manual/en/language.generators.overview.php). Usually when people think of Generators they think of the "cheap iterator" side of things, but there's another side to Generators in a feature called "Coroutines".

This basically involves exploiting the fact that PHP suspends execution of a function when it reaches a yield, only returning control when the loop advances. By calling a function that yield's outside of a loop, you get an object which you can manually advance, allowing you to effectively hold a function in suspended animation until you have a real value for it.

Mind melting no? Here's some really good links that helped me get my head around them:

- The [original PHP RFC](https://wiki.php.net/rfc/generators#sending_values) that details generators and coroutines
- [Javascript, but explains the theory well](http://jlongster.com/A-Study-on-Solving-Callbacks-with-JavaScript-Generators)
- [JS again, but also good](http://colintoh.com/blog/staying-sane-with-asynchronous-programming-promises-and-generators)

Credits
-------

[](#credits)

This library is based off of an extremely similar technique showcased by [Bastian Hofmann](https://twitter.com/BastianHofmann)in his talk at the PHPUK 2015 conference. Thanks Bastian!

###  Health Score

28

—

LowBetter than 52% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity16

Limited adoption so far

Community15

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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 ~78 days

Total

3

Last Release

3458d ago

### Community

Maintainers

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

---

Top Contributors

[![alexgisby](https://avatars.githubusercontent.com/u/231595?v=4)](https://github.com/alexgisby "alexgisby (2 commits)")

###  Code Quality

TestsPHPUnit

Code StylePHP\_CodeSniffer

### Embed Badge

![Health badge](/badges/bbc-ipr-resolver/health.svg)

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

###  Alternatives

[jetbrains/phpstorm-attributes

PhpStorm specific attributes

41417.0M776](/packages/jetbrains-phpstorm-attributes)[tareq1988/wordpress-settings-api-class

WordPress settings API Abstraction Class

46711.6k4](/packages/tareq1988-wordpress-settings-api-class)[defstudio/actions

Helpers methods for Laravel Actions

119.9k](/packages/defstudio-actions)

PHPackages © 2026

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