PHPackages                             giuseppe-mazzapica/faber - 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. giuseppe-mazzapica/faber

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

giuseppe-mazzapica/faber
========================

A tiny dependency injection container and factory class for WordPress

1.1.0(11y ago)6231MITPHPPHP &gt;=5.4

Since Aug 29Pushed 11y ago1 watchersCompare

[ Source](https://github.com/gmazzap/Faber)[ Packagist](https://packagist.org/packages/giuseppe-mazzapica/faber)[ Docs](https://github.com/Giuseppe-Mazzapica/Faber)[ RSS](/packages/giuseppe-mazzapica-faber/feed)WikiDiscussions master Synced 2d ago

READMEChangelog (3)Dependencies (2)Versions (4)Used By (0)

[![Faber](https://camo.githubusercontent.com/437a1312262efeac41f771d2754788d158ca2c115860a37ca6bc0e0f487a1b87/68747470733a2f2f676f6f676c6564726976652e636f6d2f686f73742f3042786f3462486257456b4d736455465256445270513052736247382f66616265722e706e67)](https://camo.githubusercontent.com/437a1312262efeac41f771d2754788d158ca2c115860a37ca6bc0e0f487a1b87/68747470733a2f2f676f6f676c6564726976652e636f6d2f686f73742f3042786f3462486257456b4d736455465256445270513052736247382f66616265722e706e67)

**A WordPress-specific dependency injection container that doesn't suck at *factoring* objects.**

---

It is mainly inspired to [Pimple](http://pimple.sensiolabs.org/), but allows easier object instances creation.

It is **not** a full-working plugin, it is a library to be embedded in larger projects via [Composer](https://getcomposer.org/).

As Pimple (and other DI containers), Faber manages two different kind of data: **services** and **properties**.

Properties are variables stored "as they are", that is possible to retrieve when needed.

Services are objects that are used in different part of the application, and when they are required again and again, same instance is returned. Services are registered via **factory closures**: they are [anonymous functions](http://php.net/manual/en/functions.anonymous.php) that return instances of objects.

Faber also implements factory pattern: is possible to use registered factory closures to obtain always *fresh*, vanilla instances of objects.

---

[![Travis Status](https://camo.githubusercontent.com/095f91fb53d25eaa8914f7c555062a72ffd9c3f70b43fcab1e723e7243712bae/68747470733a2f2f6170692e7472617669732d63692e6f72672f47697573657070652d4d617a7a61706963612f46616265722e737667)](https://camo.githubusercontent.com/095f91fb53d25eaa8914f7c555062a72ffd9c3f70b43fcab1e723e7243712bae/68747470733a2f2f6170692e7472617669732d63692e6f72672f47697573657070652d4d617a7a61706963612f46616265722e737667)

\#How it Works

- [Create Container Instance](#create-container-instance)
    - [Static Instantiation: The `instance()` Method](#static-instantiation-the-instance-method)
    - [Dynamic Instantiation](#dynamic-instantiation)
    - [Init Hook](#init-hook)
- [Registering and Getting Services](##registering-and-getting-services)
    - [Objects with Arguments](#objects-with-arguments)
    - [How To Force Fresh Instances](#how-to-force-fresh-instances)
    - [Get data using "Demeter chain" IDs](#get-data-using-demeter-chain-ids)
- [Registering and Getting Properties](#registering-and-getting-properties)
    - [Saving Closures as Properties](#saving-closures-as-properties)
- [Hooks](#hooks)
- [Bulk Registering](#bulk-registering)
    - [Definitions to Constructor](#definitions-to-constructor)
    - [Definitions to `load()` method](#definitions-to-load-method)
    - [Definitions to `loadFile()` method](#definitions-to-loadfile-method)
    - [Load On Init](#load-on-init)
- [Updating, Removing, Freezing and Unfreezing](#updating-removing-freezing-and-unfreezing)
    - [Updating](#updating)
    - [Removing](#removing)
    - [Freezing and Unfreezing](#freezing-and-unfreezing)
- [Fluent Interface](#fluent-interface)
- [Issers and Info Getters](#issers-and-info-getters)
    - [Conditional Methods AKA Issers](#conditional-methods-aka-issers)
    - [Info Getters](#info-getters)
- [Cached Objects](#cached-objects)
    - [Freezing and Unfreezing Cached Objects](#freezing-and-unfreezing-cached-objects)
- [About Serialization](#about-serialization)
- [Error Handling](#error-handling)

\#Requirements, Installation and License

- [Requirements](#requirements)
- [Installation](#Installation)
- [License](#license)

---

\#Create Container Instance

Before being able to do anything, an instance of container is needed. There are two ways: the *static* way and the *dynamic* one.

\##Static Instantiation: The **`instance()`** Method

First way to create an instance of the container is to use the static `instance()` method:

```
$container = GM\Faber::instance( 'my_plugin' );
```

The argument passed to instance method is the ID of the instance: is possible to create any number of instances passing different IDs, they will be completely isolated one from another:

```
$plugin_container = GM\Faber::instance( 'my_plugin' );
$theme_container = GM\Faber::instance( 'my_theme' );
```

Benefit of this approach is simple: once having a container is useless if it isn't accessible from any part of application, using this method is possible to access container from anywhere in the app by calling the `instance()` method again and again.

Note that the `instance()` method has an alias: **`i()`**:

```
$container = GM\Faber::i( 'my_app' ); // is identical to GM\Faber::instance( 'my_app' );
```

\##Dynamic Instantiation

Instances of container can be also created using *canonical* way:

```
$container = new GM\Faber();
```

When instantiated like so, a method to retrieve container instance should be arranged, maybe using a global variable:

```
global $myapp;
$myapp = new GM\Faber();
```

\##Init Hook

No matter the method used to instantiate the container, when an instance is created, an action hook is fired: **`"faber_{$id}_init"`**. The `$id` part is the instance ID.

When container is instantiated using dynamic method, to pass an ID is optional, but when no ID is passed to constructor an unique ID is automatically created, it isn't predictable, but can be retrieved using **`getId()`** method.

```
$myapp = new GM\Faber();
$id = $myapp->getId(); // will be something like 'faber_5400d85c5a18c'
```

However, when one plans to use the init hook and dynamic instantiation, is probably preferable to pass an ID to constructor like so:

```
$myapp = new GM\Faber( [], 'my_app' );
$id = $myapp->getId(); // will be 'my_app'
```

The ID is the second argument, because first is an array of properties / services to be registered during instance creation.

Init hook can be used to add properties and services to container, because it passes the just-created container instance as argument to hooking callbacks:

```
add_action( 'faber_' . $container->getId() . '_init', function( $container ) {
  // do something with container
});
```

\#Registering and Getting Services

Having an instance of container, is possible to register services to be used everywhere in app code.

A service is an object that does *something* as part of a larger system.

Services are defined by closures (anonymous functions) that return an instance of an object:

```
// define some services

$container['bar'] = function ( $cont, $args ) {
  return new My\App\Bar( $cont['foo'] );
};

$container['foo'] = function ( $cont, $args ) {
  return new My\App\Foo();
};
```

Things to note in previous code:

- array access interface is used to add services (container object is treated as an array)
- factory closures have access to the container instance that can be used to inject dependencies in the object to be created (in the example above an instance of class `Foo` is injected into `Bar` class)
- factory closures have access to the `$args` variable: it is an array of arguments used when the instance is retrieved (better explained below)
- order used to define services doesn't matter: factories are executed if and when a service is required, not when it is defined.

To get registered services is possible to use array access:

```
$bar = $container['bar']; // $bar is an instance of My\App\Bar
```

or, as alternative, the container **`get()`** method:

```
$bar = $container->get( 'bar' ); // $bar is an instance of My\App\Bar
```

Services are cached, it means that when same service is required after first time, same instance is returned:

```
$bar_1 = $container['bar'];
$bar_2 = $container->get( 'bar' );
var_dump( $bar_1 === $bar_2 ); // TRUE

$foo = $container['foo'];

// here getFoo() returns the instance of Foo injected in Bar constructor
$bar_foo = $bar_1->getFoo();
var_dump( $foo === $bar_foo ); // TRUE
```

\##Objects with Arguments

Sometimes classes require some arguments to be instantiated, and these arguments vary from instance to instance.

An example:

```
class Post {

  private $wp_post;
  private $options;

  function __construct( $id, Options $options ) {
    $this->wp_post = get_post( $id );
    $this->options = $options;
  }
}
```

Actually, this is not a *service*, however it's an object that needs a variable argument (`$id`) and a service (`$options`).

Whereas the service fits perfectly workflow explained above (an instance of `Options` can be saved in the container and passed to `Post`) is not possible to do the same thing with `$id`.

This is the limit of a lot of DI containers, but luckily not of Faber.

As example having following definitions

```
$container['post'] = function ( $cont, $args ) {
  return new Post( $args['id'], $cont['options'] );
};

$container['options'] = function ( $cont, $args ) {
  return new Options;
};
```

is possible to build some post objects like so:

```
$post_1 = $container->get( 'post', [ 'id' => 1 ] );
$post_2 = $container->get( 'post', [ 'id' => 2 ] );
$post_1_again = $container->get( 'post', [ 'id' => 1 ] );

var_dump( $post_1 === $post_2 ); // FALSE: different args => different object
var_dump( $post_1 === $post_1_again ); // TRUE: same args => same object
```

In short, `get()` method can be used to pass an array of arguments to factory closure where it is used to generate objects.

When same ID and same arguments are passed to `get()` method, the same instance is obtained; changing arguments different instances are returned.

Note: in code above the instance of `Options` passed to `Post` objects is always the same.

\##How To Force Fresh Instances

Normally, services are cached: if same arguments are passed to `get()` method then the same instance is returned.

What if a *fresh* instance of `Post` having ID 1 is required even if it has been required before?

The **`make()`** is there for the scope:

```
$post_1 = $container->get( 'post', [ 'id' => 1 ] );

$post_1_again = $container->get( 'post', [ 'id' => 1 ] );

$post_1_fresh = $container->make( 'post', [ 'id' => 1 ] );

var_dump( $post_1 === $post_1_again ); // TRUE
var_dump( $post_1 === $post_1_fresh ); // FALSE: make() force fresh instances
```

\##Get data using "Demeter chain" IDs

Assuming some classes are defined like so

```
class Foo {

  function getBar() {
    return new Bar;
  }
}

class Bar {

  function getBaz() {
    return new Baz;
  }
}

class Baz {

  function getResult() {
    return 'Result!';
  }
}
```

and first class is registered in the container like so:

```
$container['foo'] = function() {
  return new Foo;
}
```

To get call `getResult()` method of `Baz` class, is possible to do something like:

```
$result = $container['foo']->getBar()->getBaz()->getResult(); // 'Result!'
```

Fine. But (since version 1.1) in Faber is also possible to do:

```
$result = $container['foo->getBar->getBaz->getResult'] // 'Result!'
```

So is possible to access methods of objects in container by using object assigment operator (`->`) to "glue" object IDs and methods to be called *in chain*.

\#Registering and Getting Properties

Properties are usually non-object variables that need to be globally accessible in the app. They are registered and retrieved in same way of services:

```
// define properties

$container->add( 'version', '1.0.0' );
$container['timeout'] = HOUR_IN_SECONDS;
$container['app_path'] = plugin_dir_path( __FILE__ );
$container['app_assets_url'] = plugins_url( 'assets/' , __FILE__ );

// get properties

$version = $container['version'];
$timeout = $container->get( 'timeout' );
```

For properties just like for services, is possible to use `add()` or array access to register properties and `get()` or array access to retrieve them.

Additional **`prop()`** method can be used to get properties, it returns an error if used with a service ID:

```
$app_path = $container->prop( 'app_path' );
```

\##Saving Closures as Properties

When a closure is added to container, by default it's considered a factory closure for a service. To store a closure "as is", treating it as a property, is possible use the **`protect()`** method:

```
$container->protect( 'greeting', function() {
  return date('a') === 'am' ? 'Good Morning' : 'Good Evening';
});
```

\#Hooks

There are only three hooks fired inside Faber class.

The first is `"faber_{$id}_init"` explained [here](#init-hook).

The other two are filters, and they are:

- **`"faber_{$id}_get_{$which}"`**
- **`"faber_{$id}_get_prop_{$which}"`**

These filters are fired every time, respectively, a service or a property are retrieved from the container.

The variable `$id` part is the container instance ID.

The variable `$which` part is the ID of service / property being retrieved.

First argument they pass to hooking callbacks is the value just retrieved from container. Second argument is the instance of container itself.

```
$container = GM\Faber::i( 'test' );

$container['foo'] => 'foo';

$container['bar'] => function() {
  new Bar;
};

$foo = $container['foo']; // result passes through 'faber_test_get_prop_foo' filter

$bar = $container['bar']; // result passes through 'faber_test_get_bar' filter
```

\#Bulk Registering

Instead of registering services one by one using array access or `add()` method, is possible to register more services (and properties) at once. That can be done in three ways:

- passing an array of definitions to constructor (only when using dynamic instantiation method)
- passing an array of definitions to `load()` method
- passing the path of a PHP file that returns an array of definitions to `loadFile()` method

\##Definitions to Constructor

```
$def = [
  'timeout' => HOUR_IN_SECONDS,
  'version' => '1.0.0',
  'foo' => function( $container, $args ) {
    return new Foo;
  },
  'bar' => function( $container, $args ) {
    return new Bar( $container['foo'] );
  }
];

$container = new GM\Faber( $def );
```

\##Definitions to `load()` method

```
$container = new GM\Faber;
$container->load( $def ); // $def is same of above
```

\##Definitions to `loadFile()` method

First a definitions file is needed, something like:

```
