Skip to content

Commit

Permalink
Merge pull request #232 from laminas/4.0.x-merge-up-into-4.1.x_ejG6mt8F
Browse files Browse the repository at this point in the history
Merge release 4.0.1 into 4.1.x
  • Loading branch information
Ocramius authored Apr 3, 2024
2 parents db2250c + d6ebf7b commit 2329754
Show file tree
Hide file tree
Showing 24 changed files with 577 additions and 225 deletions.
3 changes: 3 additions & 0 deletions docs/book/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# This Is Only a Placeholder

The content of this page is automatically generated.
6 changes: 3 additions & 3 deletions docs/book/v3/cookbook/factories-vs-abstract-factories.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ The service manager is optimized to locate *factories*, as it can do an
immediate hash table lookup; abstract factories involve:

- Looping through each abstract factory
- invoking its method for service location
- if the service is located, using the factory
- invoking its method for service location
- if the service is located, using the factory

This means, internally:

- a hash table lookup (for the abstract factory)
- invocation of 1:N methods for discovery
- which may contain additional lookups and/or retrievals in the container
- which may contain additional lookups and/or retrievals in the container
- invocation of a factory method (assuming successful lookup)

As such, having an explicit map can aid performance dramatically.
Expand Down
10 changes: 0 additions & 10 deletions docs/book/v3/index.html

This file was deleted.

1 change: 0 additions & 1 deletion docs/book/v3/index.md

This file was deleted.

146 changes: 146 additions & 0 deletions docs/book/v3/lazy-services.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Lazy Services

`Laminas\ServiceManager` can use [delegator factories](delegators.md) to generate
"lazy" references to your services.

Lazy services are [proxies](http://en.wikipedia.org/wiki/Proxy_pattern) that
get lazily instantiated, and keep a reference to the real instance of
the proxied service.

## Use cases

You may want to lazily initialize a service when it is instantiated very often,
but not always used.

A typical example is a database connection: it is a dependency to many other
elements in your application, but that doesn't mean that every request will
execute queries through it.

Additionally, instantiating a connection to the database may require some time
and eat up resources.

Proxying the database connection would allow you to delay that overhead until the
object is really needed.

## Setup

`Laminas\ServiceManager\Proxy\LazyServiceFactory` is a [delegator factory](delegators.md)
capable of generating lazy loading proxies for your services.

The lazy service facilities depend on [ProxyManager](https://github.com/FriendsOfPHP/proxy-manager-lts);
you will need to install that package before using the feature:

```php
$ composer require friendsofphp/proxy-manager-lts
```

## Practical example

To demonstrate how a lazy service works, you may use the following `Buzzer`
example class, which is designed to be slow at instantiation time for
demonstration purposes:

```php
namespace MyApp;

class Buzzer
{
public function __construct()
{
// deliberately halting the application for 5 seconds
sleep(5);
}

public function buzz()
{
return 'Buzz!';
}
}
```

You can then proceed and configure the service manager to generate proxies
instead of real services:

```php
use MyApp\Buzzer;
use Laminas\ServiceManager\Factory\InvokableFactory;
use Laminas\ServiceManager\Proxy\LazyServiceFactory;
use Laminas\ServiceManager\ServiceManager;

$serviceManager = new \Laminas\ServiceManager\ServiceManager([
'factories' => [
Buzzer::class => InvokableFactory::class,
],
'lazy_services' => [
// Mapping services to their class names is required
// since the ServiceManager is not a declarative DIC.
'class_map' => [
Buzzer::class => Buzzer::class,
],
],
'delegators' => [
Buzzer::class => [
LazyServiceFactory::class,
],
],
]);
```

This configuration tells the service manager to add the add
`LazyServiceFactory` as a delegator for `Buzzer`.

You can now retrieve the buzzer:

```php
use MyApp\Buzzer;

$buzzer = $serviceManager->get(Buzzer::class);
echo $buzzer->buzz();
```

To verify that the proxying occurred correctly, you can run the following code,
which should delay the 5 seconds wait time hardcoded in `Buzzer::__construct`
until `Buzzer::buzz` is invoked:

```php
use MyApp\Buzzer;

for ($i = 0; $i < 100; $i += 1) {
$buzzer = $serviceManager->get(Buzzer::class);
echo "created buzzer $i\n";
}

echo $buzzer->buzz();
```

## Configuration

This is the config structure expected by `Laminas\ServiceManager\Proxy\LazyServiceFactory`,
in the `lazy_services` key passed in the service manager configuration:

```php
[
// map of service names and their relative class names - this
// is required since the service manager cannot know the
// class name of defined services up front
'class_map' => [
// 'foo' => 'MyApplication\Foo',
],

// directory where proxy classes will be written - default to system_get_tmp_dir()
'proxies_target_dir' => null,

// namespace of the generated proxies, default to "ProxyManagerGeneratedProxy"
'proxies_namespace' => null,

// whether the generated proxy classes should be written to disk or generated on-the-fly
'write_proxy_files' => false,
];
```

After you have an instance, you can map lazy service/class pairs using
`mapLazyService()`:

```php
$container->mapLazyService('foo', \MyApplication\Foo::class);
```
4 changes: 2 additions & 2 deletions docs/book/v3/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -952,9 +952,9 @@ In addition, review the following changes.
#### Constructor

- The constructor now accepts the following arguments, in the following order:
- The parent container instance; this is usually the application-level
- The parent container instance; this is usually the application-level
`ServiceManager` instance.
- Optionally, an array of configuration for the plugin manager instance; this
- Optionally, an array of configuration for the plugin manager instance; this
should have the same format as for a `ServiceManager` instance.
- `validatePlugin()` was renamed to `validate()` (now defined in
`PluginManagerInterface`). The `AbstractPluginManager` provides
Expand Down
92 changes: 92 additions & 0 deletions docs/book/v3/plugin-managers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Plugin managers

Plugin managers are *specialized* service managers, typically used to create
homogeneous objects of a specific type.

Because a plugin manager extends a service manager, it works the same and can
be configured similarly. It provides a separation of concerns (it will be used
in specific contexts), and provides additional instance validation.

Laminas components extensively use plugin managers to create services
that share common functionalities. For instance, all validator services are
specified inside a specialized `ValidatorPluginManager`.

## Creating a plugin manager

To create a plugin manager, you first need to create a new class that extends
`Laminas\ServiceManager\AbstractPluginManager`:

```php
class ValidatorPluginManager extends AbstractPluginManager
{
protected $instanceOf = ValidatorInterface::class;
}
```

The `$instanceOf` variable specifies a class/interface type that all instances
retrieved from the plugin manager must fulfill. If an instance created by the
plugin manager does not match, a `Laminas\ServiceManager\Exception\InvalidServiceException`
exception will be thrown.

Most of the time, this shortcut is enough. However if you have more complex
validation rules, you can override the `validate()` method:

```php
class ValidatorPluginManager extends AbstractPluginManager
{
public function validate($instance)
{
if ($instance instanceof Foo || $instance instanceof Bar) {
return;
}

throw new InvalidServiceException('This is not a valid service!');
}
}
```

## Configuring a plugin manager

A plugin manager requires that you pass a parent service manager (typically,
the application's service manager) as well as service configuration. Service
configuration follows the exact same pattern as for a normal service manager;
refer to the [configuring the service manager](configuring-the-service-manager.md) section for details.

Because a plugin manager is often a service itself, we recommend you to
register the plugin manager as part of the general service manager, as shown
below:

```php
$serviceManager = new ServiceManager([
'factories' => [
ValidatorPluginManager::class => function(ContainerInterface $container, $requestedName) {
return new ValidatorPluginManager($container, [
'factories' => [
StringLengthValidator::class => InvokableFactory::class,
],
]);
},
],
]);

// Get the plugin manager:

$pluginManager = $serviceManager->get(ValidatorPluginManager::class);

// Use the plugin manager

$validator = $pluginManager->get(StringLengthValidator::class);
```

> Unlike the version 2 implementation, when inside the context of the factory
> of a service created by a plugin manager, the passed container **will not
> be** the plugin manager, but the parent service manager instead. If you need
> access to other plugins of the same type, you will need to fetch the plugin
> manager from the container:
>
> ```php
> function ($container, $name, array $options = []) {
> $validators = $container->get(ValidatorPluginManager::class);
> // ...
> }
> ```
Loading

0 comments on commit 2329754

Please sign in to comment.