Skip to content

Commit

Permalink
Merge pull request #165 from mnapoli/4.2
Browse files Browse the repository at this point in the history
4.2
  • Loading branch information
mnapoli committed Jul 29, 2014
2 parents c3c5700 + 27cc129 commit 66bb638
Show file tree
Hide file tree
Showing 54 changed files with 2,066 additions and 384 deletions.
26 changes: 26 additions & 0 deletions change-log.md
@@ -1,5 +1,31 @@
# Change log

## 4.2

Read the [news entry](news/10-php-di-4-2-released.md).

**Minor BC-break**: Optional parameters (that were not configured) were injected, they are now ignored, which is what naturally makes sense since they are optional.
Example:

```php
public function __construct(Bar $bar = null)
{
$this->bar = $bar ?: $this->createDefaultBar();
}
```

Before 4.2, PHP-DI would try to inject a `Bar` instance. From 4.2 and onwards, it will inject `null`.

Of course, you can still explicitly define an injection for the optional parameters and that will work.

All changes:

* [#162](https://github.com/mnapoli/PHP-DI/pull/162) Added `Container::call()` to call functions with dependency injection
* [#156](https://github.com/mnapoli/PHP-DI/issues/156) Wildcards (`*`) in definitions
* [#164](https://github.com/mnapoli/PHP-DI/issues/164) Prototype scope is now available for `factory()` definitions too
* FIXED [#168](https://github.com/mnapoli/PHP-DI/pull/168) `Container::has()` now returns false for interfaces and abstract classes that are not mapped in the definitions
* FIXED [#171](https://github.com/mnapoli/PHP-DI/issues/171) Optional parameters are now ignored (not injected) if not set in the definitions (see the BC-break warning above)

## 4.1

Read the [news entry](news/09-php-di-4-1-released.md).
Expand Down
3 changes: 3 additions & 0 deletions composer.json
Expand Up @@ -24,5 +24,8 @@
"ocramius/proxy-manager": "~0.3",
"symfony/yaml": "2.*",
"container-interop/container-interop": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
}
}
1 change: 1 addition & 0 deletions doc/README.md
Expand Up @@ -14,6 +14,7 @@ tab: index
## PHP-DI's documentation

* [Configure the container](container-configuration.md)
* [Use the container](container.md)
* [Define injections](definition.md)
* [Definition overriding](definition-overriding.md)
* [Lazy injection](lazy-injection.md)
Expand Down
200 changes: 200 additions & 0 deletions doc/container.md
@@ -0,0 +1,200 @@
---
template: documentation
tab: container
---

# Using the container

This documentation describes the API of the container object itself.

## get() & has()

The container implements the standard [ContainerInterop](https://github.com/container-interop/container-interop).
That means it implements `Interop\Container\ContainerInterface`:

```php
namespace Interop\Container;

interface ContainerInterface
{
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $id Identifier of the entry to look for.
* @throws NotFoundException No entry was found for this identifier.
* @throws ContainerException Error while retrieving the entry.
* @return mixed Entry.
*/
public function get($id);

/**
* Returns true if the container can return an entry for the given identifier.
*
* @param string $id Identifier of the entry to look for.
* @return boolean
*/
public function has($id);
}
```

If you type-hint against this interface instead of the implementation (`DI\Container`),
that means you will be able to use another container (compatible with ContainerInterop)
later.

## set()

Of course you can set entries on the container:

```php
$container->set('foo', 'bar');
```

However it is recommended to use definition files.
See the [definition documentation](definition.md).

## make()

The container also offers a `make` method. This method is defined in the `DI\FactoryInterface`:

```php
interface FactoryInterface
{
/**
* Resolves an entry by its name. If given a class name, it will return a new instance of that class.
*
* @param string $name Entry name or a class name.
* @param array $parameters Optional parameters to use to build the entry. Use this to force specific
* parameters to specific values. Parameters not defined in this array will
* be automatically resolved.
*
* @throws DependencyException Error while resolving the entry.
* @throws NotFoundException No entry or class found for the given name.
* @return mixed
*/
public function make($name, array $parameters = array());
}
```

The `make()` method works the same as `get()`, except it will create a new instance each time.
It will use the parameters provided for the constructor, and the missing parameters will be
resolved from the container.

It is very useful to create objects that do not belong *inside* the container (i.e. that are not services),
but that may have dependencies. It is also useful if you want to override some parameters in the constructor.
For example controllers, models, …

If you need to use the `make()` method inside a service, or a controller, or whatever, it is
recommended you type-hint against `FactoryInterface`. That avoids coupling your code to the container.

## call()

Since version 4.2, the container exposes a `call()` method:

```php
$container->call(function (Logger $logger, EntityManager $em) {
// ...
});
```

The parameters are resolved as container entries using autowiring.

If some parameters shouldn't be resolved by the container, or if some can't be resolved
using autowiring (for example if a parameter is not type-hinted), then you must define those
parameters in an array:

```php
$parameters = [
'data' => /* some variable */
];

$container->call(function (Logger $logger, $data) {
// ...
}, $parameters);
```

As you can see, you can mix explicitly defined parameters (i.e. `$data` above)
and auto-resolved parameters (i.e. the `$logger`).

Note that you can also define injections on the fly if you don't use type-hints:

```php
$parameters = [
'logger' => \DI\link('Logger')
];

$container->call(function ($logger) {
// ...
}, $parameters);
```

The `call()` method is useful to invoke controllers defined as closures, for example:

```php
$requestParameters = $_GET;

$controller = function ($id, EntityManager $em) {
// ...
}

$container->call($controller, $requestParameters);
```

This leaves the liberty to the developer writing controllers to get request parameters
*and* services using dependency injection.

As with `make()`, `call()` is defined in `DI\InvokerInterface` so that you can type-hint
against that interface without coupling yourself to the container.

```php
namespace DI;

interface InvokerInterface
{
/**
* Call the given function using the given parameters.
*
* @param callable $callable Function to call.
* @param array $parameters Parameters to use.
* @return mixed Result of the function.
*/
public function call($callable, array $parameters = []);
}
```

## injectOn()

Sometimes you want to inject dependencies on an object that is already created.

For example, some old frameworks don't allow you to control how controllers are created.
With `injectOn`, you can ask the container to fulfill the dependencies after the object is created.

Keep in mind it's usually always better to use `get()` or `make()` instead of `injectOn()`,
use it only where you really have to.

Example:

```php
class UserController extends BaseController
{
/**
* @Inject
* @var SomeService
*/
private $someService;

public function __construct()
{
// The framework doesn't let us control how the controller is created, so
// we can't use the container to create the controller
// So we ask the container to inject dependencies
$container->injectOn($this);

// Now the dependencies are injected
$this->someService->doSomething();
}
}
```

As you might have guessed, you can't use constructor injection with this method.
But other kind of injections (property or setter) will work, whether you use annotations
or whether you configured your object in a definition file.
26 changes: 26 additions & 0 deletions doc/definition.md
Expand Up @@ -214,6 +214,12 @@ return [
return new MyClass($c->get('db.host'));
}),

// We can set the scope on the factory too
// This will return a new object each time we request SomeOtherClass
'SomeOtherClass' => DI\factory(function () {
return new SomeOtherClass();
})->scope(Scope::PROTOTYPE()),

// Defining an alias to another entry
'some.entry' => DI\link('some.other.entry'),

Expand All @@ -237,3 +243,23 @@ $container->set('My\Class', \DI\object()
```

The API is the same as shown above for the PHP array containing definitions.

### Wildcards

You can use wildcards to define a batch of entries. It can be very useful to bind interfaces to implementations:

```php
return [
'Blog\Domain\*RepositoryInterface' => DI\object('Blog\Architecture\*DoctrineRepository'),
];
```

In our example, the wildcard will match `Blog\Domain\UserRepositoryInterface`, and it will map it to
`Blog\Architecture\UserDoctrineRepository`.

Good to know:

- the wildcard does not match across namespaces
- an exact match (i.e. without `*`) will always be chosen over a match with a wildcard
(first PHP-DI looks for an exact match, then it searches in the wildcards)
- in case of "conflicts" (i.e. 2 different matches with wildcards), the first match will prevail

0 comments on commit 66bb638

Please sign in to comment.