Creación de extensiones para Nette DI
Al generar un contenedor DI además de los ficheros de configuración también afectan las llamadas
extensiones. Las activamos en el fichero de configuración en la sección extensions
.
Así es como añadimos la extensión representada por la clase BlogExtension
con nombre blog
:
extensions:
blog: BlogExtension
Cada extensión de compilador hereda de Nette\DI\CompilerExtension y puede implementar los siguientes métodos que son llamados durante la compilación DI:
- getConfigSchema()
- loadConfiguration()
- beforeCompile()
- afterCompile()
getConfigSchema()
Este método se ejecuta en primer lugar. Define el esquema utilizado para validar los parámetros de configuración.
Las extensiones se configuran en una sección cuyo nombre es el mismo bajo el que se añadió la extensión, por ejemplo
blog
.
# same name as my extension
blog:
postsPerPage: 10
comments: false
Definiremos un esquema describiendo todas las opciones de configuración, incluyendo sus tipos, valores aceptados y posiblemente valores por defecto:
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),
]);
}
}
Consulte la documentación Schema. Además, puedes especificar qué opciones pueden
ser dynamic usando dynamic()
, por
ejemplo Expect::int()->dynamic()
.
Accedemos a la configuración a través de $this->config
, que es un objeto stdClass
:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$num = $this->config->postPerPage;
if ($this->config->allowComments) {
// ...
}
}
}
loadConfiguration()
Este método se utiliza para añadir servicios al contenedor. Esto se hace mediante 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']) // or setCreator()
->addSetup('setLogger', ['@logger']);
}
}
La convención es prefijar los servicios añadidos por una extensión con su nombre para que no surjan conflictos de nombres.
Esto se hace mediante prefix()
, de forma que si la extensión se llama blog
, el servicio se llamará
blog.articles
.
Si necesitamos renombrar un servicio, podemos crear un alias con su nombre original para mantener la compatibilidad hacia
atrás. De forma similar, esto es lo que hace Nette para, por ejemplo, routing.router
, que también está disponible
con el nombre anterior router
.
$builder->addAlias('router', 'routing.router');
Recuperar servicios de un archivo
Podemos crear servicios utilizando la API de ContainerBuilder, pero también podemos añadirlos a través del conocido archivo
de configuración de NEON y su sección services
. 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)
Añadiremos los servicios de esta forma
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
// load the configuration file for the extension
$this->compiler->loadDefinitionsFromConfig(
$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
);
}
}
beforeCompile()
El método es llamado cuando el contenedor contiene todos los servicios añadidos por las extensiones individuales en los
métodos loadConfiguration
así como los ficheros de configuración del usuario. En esta fase de ensamblaje, podemos
modificar las definiciones de los servicios o añadir enlaces entre ellos. Puedes utilizar el método findByTag()
para buscar servicios por etiquetas, o el método findByType()
para buscar por clase o interfaz.
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 fase, la clase contenedora ya está generada como un objeto ClassType, contiene todos los métodos que crea el servicio, y está lista para ser cacheada como fichero PHP. Todavía podemos editar el código de la clase resultante en este punto.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$method = $class->getMethod('__construct');
// ...
}
}
$initialization
El Configurador llama al código de inicialización después de container creation, que se crea escribiendo en un objeto
$this->initialization
usando method addBody().
Vamos a mostrar un ejemplo de cómo iniciar una sesión o iniciar servicios que tienen la etiqueta run
usando
código de inicialización:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
// inicio automático de sesión
if ($this->config->session->autoStart) {
$this->initialization->addBody('$this->getService("session")->start()');
}
// los servicios con la etiqueta "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]);
}
}
}