Creación de extensiones para Nette DI
La generación del contenedor DI, además de los archivos de configuración, también está influenciada por las
llamadas extensiones. Las activamos en el archivo de configuración en la sección extensions
.
Así es como agregamos una extensión representada por la clase BlogExtension
bajo el nombre
blog
:
extensions:
blog: BlogExtension
Cada extensión del compilador hereda de Nette\DI\CompilerExtension y puede implementar los siguientes métodos, que se llaman secuencialmente durante la construcción del contenedor DI:
- getConfigSchema()
- loadConfiguration()
- beforeCompile()
- afterCompile()
getConfigSchema()
Este método se llama primero. Define el esquema para validar los parámetros de configuración.
Configuramos la extensión en la sección cuyo nombre es el mismo que aquel bajo el cual se agregó la extensión, es decir,
blog
:
# mismo nombre que la extensión
blog:
postsPerPage: 10
allowComments: false
Creamos un esquema que describe todas las opciones de configuración, incluidos sus tipos, valores permitidos y, opcionalmente, valores predeterminados:
use Nette\Schema\Expect;
class BlogExtension extends Nette\DI\CompilerExtension
{
public function getConfigSchema(): Nette\Schema\Schema
{
return Expect::structure([
'postsPerPage' => Expect::int(),
'allowComments' => Expect::bool()->default(true),
]);
}
}
Encontrará la documentación en la página Schema. Además, se puede especificar
qué opciones pueden ser dinámicas usando
dynamic()
, por ejemplo, Expect::int()->dynamic()
.
Accedemos a la configuración a través de la variable $this->config
, que es un objeto
stdClass
:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$num = $this->config->postsPerPage;
if ($this->config->allowComments) {
// ...
}
}
}
loadConfiguration()
Se utiliza para agregar servicios al contenedor. Para esto se utiliza Nette\DI\ContainerBuilder:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
$builder->addDefinition($this->prefix('articles'))
->setFactory(App\Model\HomepageArticles::class, ['@connection']) // o setCreator()
->addSetup('setLogger', ['@logger']);
}
}
La convención es prefijar los servicios agregados por la extensión con su nombre para evitar conflictos de nombres. Esto lo
hace el método prefix()
, por lo que si la extensión se llama blog
, el servicio se llamará
blog.articles
.
Si necesitamos renombrar un servicio, podemos crear un alias con el nombre original para mantener la compatibilidad hacia
atrás. Nette hace algo similar, por ejemplo, con el servicio routing.router
, que también está disponible bajo el
nombre anterior router
.
$builder->addAlias('router', 'routing.router');
Carga de servicios desde un archivo
No tenemos que crear servicios solo usando la API de la clase ContainerBuilder, sino también con la notación familiar
utilizada en el archivo de configuración NEON en la sección de servicios. El prefijo @extension
representa la
extensión actual.
services:
articles:
create: MyBlog\ArticlesModel(@connection)
comments:
create: MyBlog\CommentsModel(@connection, @extension.articles)
articlesList:
create: MyBlog\Components\ArticlesList(@extension.articles)
Cargamos los servicios:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
// cargar el archivo de configuración para la extensión
$this->compiler->loadDefinitionsFromConfig(
$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
);
}
}
beforeCompile()
El método se llama cuando el contenedor contiene todos los servicios agregados por las extensiones individuales en los
métodos loadConfiguration
y también por los archivos de configuración del usuario. En esta etapa de construcción,
podemos modificar las definiciones de servicios o agregar enlaces entre ellos. Para buscar servicios en el contenedor por tags,
se puede usar el método findByTag()
, y por clase o interfaz, el método findByType()
.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function beforeCompile()
{
$builder = $this->getContainerBuilder();
foreach ($builder->findByTag('logaware') as $serviceName => $tagValue) {
$builder->getDefinition($serviceName)->addSetup('setLogger');
}
}
}
afterCompile()
En esta etapa, la clase del contenedor ya está generada en forma de objeto ClassType, contiene todos los métodos que crean servicios y está lista para ser escrita en la caché. Todavía podemos modificar el código de la clase resultante en este momento.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$method = $class->getMethod('__construct');
// ...
}
}
$initialization
La clase Configurator, después de crear el contenedor,
llama al código de inicialización, que se crea escribiendo en el objeto $this->initialization
usando el método addBody().
Mostraremos un ejemplo de cómo iniciar la sesión con código de inicialización o ejecutar servicios que tienen el tag
run
:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
// inicio automático de la sesión
if ($this->config->session->autoStart) {
$this->initialization->addBody('$this->getService("session")->start()');
}
// los servicios con el tag run deben crearse después de instanciar el contenedor
$builder = $this->getContainerBuilder();
foreach ($builder->findByTag('run') as $name => $foo) {
$this->initialization->addBody('$this->getService(?);', [$name]);
}
}
}