Configuração
1Apresentação
A configuração de um projeto que usa o Temma é feita modificando o arquivo temma.php, que fica no diretório etc/ e que possui várias seções.
Por padrão, o Temma procura um arquivo chamado etc/temma.php, no formato PHP (explicado a seguir). Mas você pode usar outros formatos para o arquivo de configuração:
- JSON (arquivo etc/temma.json): Esse formato é amplamente usado em projetos de TI. É o formato histórico do Temma, e ainda é recomendado se você quiser gerar seus arquivos de configuração por meio de um script.
- YAML (arquivo etc/temma.yaml): Esse formato é cada vez mais usado, principalmente por outros frameworks modernos.
- NEON (arquivo etc/temma.neon): Formato usado pelo framework Nette e pela ferramenta de análise estática de código PHPStan. É muito semelhante ao formato YAML.
Recomendamos usar o formato PHP, pois ele tem a vantagem de ser armazenado em cache pelo OPcache (o cache de OPcode, que evita a necessidade de reler os arquivos PHP a cada acesso), acelerando assim o processamento. Ele também permite a geração dinâmica de configuração.
No diretório etc/, você encontrará quatro arquivos usados para ilustrar a configuração de um projeto Temma:
- temma-mini.php: Arquivo mínimo contendo as diretivas básicas (formato PHP).
- temma-mini.json: Arquivo mínimo contendo as diretivas básicas (formato JSON).
- temma-mini.yaml: Arquivo mínimo contendo as diretivas básicas (formato YAML).
- temma-full.php: Arquivo mostrando todas as opções disponíveis (formato PHP).
2Arquivo temma.php simples
Aqui está um exemplo típico de um arquivo etc/temma.php mínimo:
<?php
return [
// configuração da aplicação
'application' => [
'dataSources' => [
'db' => 'mysql://user:passwd@localhost/mybase'
],
'rootController' => 'Homepage'
],
// definição dos níveis de log
'loglevels' => 'ERROR',
// definição das páginas de erro
'errorPages' => 'error404.html',
// dados importados automaticamente como variáveis de template
'autoimport' => [
'googleId' => 'azeazeaez',
'googleAnalyticsId' => 'azeazeazeaze',
]
];
Podemos observar:
- Linha 7: A definição do DSN que permite se conectar ao banco de dados.
- Linha 9: O nome do controlador raiz, que será chamado quando alguém acessar o site sem especificar um controlador.
- Linha 12: Definição do limite para gravação de mensagens de log no arquivo log/temma.log. Aqui, com o limite "ERROR", apenas as mensagens com os níveis ERROR e CRIT serão de fato gravadas no arquivo de log.
- Linha 14: Definição da página a ser exibida caso ocorra um erro. A página deve ficar armazenada no diretório www/ do projeto.
-
Variáveis de template importadas automaticamente.
- Linha 17: ID do Google, disponível nos templates com {$conf.googleId} e no controlador com $this['conf']['googleId'].
- Linha 18: ID do Google Analytics, disponível nos templates com {$conf.googleAnalyticsId} e no controlador com $this['conf']['googleAnalyticsId'].
3Arquivo temma.php completo
Aqui está outro exemplo, que mostra todas as variáveis de configuração padrão:
<?php
return [
// Variáveis gerais de definição da aplicação
'application' => [
// fontes de dados
'dataSources' => [
// DSN de conexão com o banco de dados MySQL
// (ver objeto \Temma\Datasources\Sql).
// Opcional
'db' => 'mysqli://user:passwd@localhost/mybase',
// DSN de conexão com o banco de dados Redis
// (ver objeto \Temma\Datasources\Redis).
// Opcional
'ndb' => 'redis://localhost:6379/0',
// DSN de conexão com o servidor de cache Memcached
// (ver objeto \Temma\Datasources\Memcache).
// Opcional
'cache' => 'memcache://localhost:11211',
],
// Indica se as sessões devem ou não ser usadas.
// Use para desativar as sessões quando necessário.
// Opcional: "true" por padrão.
'enableSessions' => true,
// Nome do cookie de sessão.
// Opcional: "TemmaSession" por padrão.
'sessionName' => 'TemmaSession',
// Fonte de dados para armazenar as sessões.
// Opcional: Usa o mecanismo nativo de sessões
// do PHP por padrão
'sessionSource' => 'ndb',
// Duração da sessão.
// Opcional: Um ano por padrão.
'sessionDuration' => 31536000,
// Indica se o cookie de sessão deve ser enviado apenas em
// conexões seguras por HTTPS.
// Opcional: "false" por padrão.
'sessionSecure' => false,
// Nome de domínio associado ao cookie de sessão.
// Opcional: não definido por padrão.
'cookieDomain' => null,
// Namespace padrão dos controladores
// Opcional: por padrão os controladores ficam
// no namespace global.
'defaultNamespace' => '\MyApp\Controllers',
// Nome do controlador usado para a raiz do site.
// Opcional. Por padrão usa o controlador definido pela
// variável 'defaultController'.
'rootController' => 'Homepage',
// Nome do controlador padrão a ser usado se o controlador
// solicitado não existir.
// Opcional. Por padrão, gera um erro 404 se for solicitado
// um controlador que não existe.
'defaultController' => 'NotFound',
// Nome do controlador a ser sempre chamado,
// mesmo se o solicitado existir.
// Opcional.
'proxyController' => 'Main',
// Nome da view padrão.
// Opcional. \Temma\Views\Smarty por padrão.
'defaultView' => '\Temma\Views\Smarty',
// Nome do objeto que será usado como
// componente de injeção de dependências
// Opcional: Usa o componente de injeção de dependências
// do Temma por padrão.
'loader' => 'MyLoader',
// Caminho para o arquivo de log.
// Opcional: Por padrão, os logs são gravados no
// arquivo "log/temma.log".
// Coloque um valor falso (null, false, string vazia ou o
// número zero) para desativar a gravação no arquivo de log.
'logFile' => 'log/temma.log',
// Nome do objeto de gerenciamento de log, ou lista de nomes de objetos.
// Opcional: Nenhum gerenciador de log é ativado por padrão.
'logManager' => [ 'ElasticLogManager', 'SentryLogManager' ],
],
// Definição dos níveis de log
'loglevels' => [
'Temma/Base' => 'ERROR',
'Temma/Web' => 'WARN',
'myapp' => 'DEBUG',
'default' => 'NOTE',
],
// Definição dos níveis de log para mensagens em buffer
'bufferingLoglevels' => [
'Temma/Base' => 'DEBUG',
'myapp' => 'DEBUG',
],
// Roteamento: Indicamos os nomes dos controladores virtuais,
// associando-os a um controlador real (ou virtual, em cascata),
// que tratará as requisições.
'routes' => [
'sitemap.xml' => 'SitemapController',
'sitemap.extended.xml' => 'sitemap.xml',
'robert' => 'BobController',
],
// Gerenciamento de plugins
'plugins' => [
// Plugins executados para todos os controladores
// - plugins executados antes do controlador
'_pre' => [
'CheckRequest',
'UserGrant',
],
// - plugins executados depois do controlador
'_post' => [ 'AddCrossLinks' ],
// Definição de plugins específicos do controlador Article
'Article' => [
// plugins executados antes e depois do controlador
'_pre' => [ 'Something' ],
'_post' => [ 'SomethingElse' ],
// plugins específicos da ação index
'index' => [
'_pre' => [ 'Aaa' ],
'_post' => [ 'Bbb' ],
],
// plugin executado antes da ação setData
'setData' => [
'_pre' => [ 'CccPlugin' ]
]
],
// Plugin do controlador BobController, mas
// apenas quando chamado pela sua rota "robert"
'robert' => [
'_pre' => [ 'AnotherPlugin' ]
],
],
// Definição das páginas de erro
'errorPages' => [
'404' => 'error404.html',
'500' => 'error500.html',
'default' => 'error404.html'
],
// Lista de caminhos de inclusão para o autoloader, com ou sem prefixo de namespace
'includePaths' => [
'/opt/some_library/lib',
'/opt/other_lib',
'\Acme\Log' => '/path/to/acme-log/lib',
'\MyLib\Feature' => '/path/to/vendors/feature',
],
// Lista de caminhos de inclusão do PHP
'phpIncludePaths' => [
'/usr/share/php',
],
// Dados importados automaticamente como variáveis de template
'autoimport' => [
'googleId' => 'azeazeaez',
'googleAnalyticsId' => 'azeazeazeaze',
],
// Definição dos contratos usados para validação de dados
'validationTypes' => [
'sitecolor' => 'enum; values: orange, yellow, lime, green',
'user' => [
'type' => 'assoc',
'keys' => [
'id?' => 'int',
'login' => 'string',
'email' => 'email',
],
],
'category' => '\App\Validations\CategoryValidator',
],
// Configuração estendida
'x-homepage' => [
'title' => 'Título do site',
'description' => 'Descrição do site',
],
// Outra configuração estendida
'x-email' => [
'senderAddress' => 'admin@localhost.localdomain',
'senderName' => 'Administrador',
]
];
4Seções de configuração
4.1application
A principal seção do arquivo é a chamada application. Ela é usada para definir as variáveis de configuração
mais importantes do projeto.
Aqui estão as diferentes variáveis que ela pode conter:
- dataSources: Usada para definir os DSNs (Data Source Name) das conexões com fontes de dados (MySQL, Redis, Memcached…).
- enableSessions: Essa variável deve ser definida como false se você não quiser gerenciar as sessões. Isso pode ser útil quando você não quer rastrear as visitas dos usuários, como no caso de uma API.
- sessionName: Nome do cookie que conterá o identificador de sessão.
- sessionSource: Nome da fonte de dados (no array associativo dataSources) que conterá os dados da sessão. Pode ser uma conexão Redis, Memcache ou SQL (ou até File ou S3). Se esse parâmetro não estiver definido, o Temma usa o mecanismo nativo de sessões do PHP.
- sessionDuration: Duração da sessão em segundos.
- sessionSecure: Booleano indicando se o cookie de sessão deve ser enviado apenas em conexões seguras por HTTPS.
-
cookieDomain: Nome de domínio associado ao cookie de sessão. Várias possibilidades:
-
Se o parâmetro não estiver definido, o domínio de nível 1 (sem subdomínios) do site atual é usado.
O navegador enviará o cookie para o domínio e todos os seus subdomínios (e sub-subdomínios etc.).
Por exemplo, se o site atual for www.site.com, o domínio site.com será usado. O navegador enviará o cookie (e a sessão ficará, portanto, acessível) para os sites site.com, www.site.com, admin.site.com, www.users.site.com, etc. - Se o parâmetro estiver definido mas vazio (string vazia, false ou null), nenhum domínio será associado ao cookie. O navegador então usará o valor exato do domínio atual (por exemplo, admin.user.site.com). O cookie não será enviado para domínios pais (user.site.com e site.com), nem para domínios filhos (www.admin.user.site.com).
- Se o parâmetro estiver definido e não vazio, esse será o valor associado ao cookie. Nesse caso, a sessão será compartilhada para esse domínio e todos os seus subdomínios.
-
Se o parâmetro não estiver definido, o domínio de nível 1 (sem subdomínios) do site atual é usado.
O navegador enviará o cookie para o domínio e todos os seus subdomínios (e sub-subdomínios etc.).
- defaultNamespace: Caso os arquivos PHP dos controladores não sejam salvos no diretório
controlers/, essa variável contém o namespace padrão dos controladores.
Por exemplo, se essa variável contiver o valor \App\Ctrl, e acessarmos a URL www.mysite.com/home, o Temma carregará o objeto \App\Ctrl\Home. - rootController: Nome do controlador a ser executado quando uma conexão é feita na raiz do site.
- defaultController: Nome do controlador a ser executado quando é solicitado um controlador que não existe.
- proxyController: Nome do controlador a ser executado sempre, mesmo que o controlador solicitado exista.
- defaultView: Nome da view padrão, que será usada para gerar o fluxo de saída (a menos que outra view seja explicitamente solicitada no controlador ou em um plugin).
- loader: Nome do objeto que será usado como componente de injeção de dependências.
- logFile: Caminho para o arquivo de log. Coloque um valor falso (null, false, string vazia ou o número zero) para desativar a gravação no arquivo de log (isso pode ser útil se você quiser usar apenas um gerenciador de log; veja a próxima diretiva). Qualquer caminho que não comece com uma barra (/) será relativo à raiz do projeto.
- logManager: Essa variável pode conter o nome de um objeto de gerenciamento de log, ou uma lista de nomes de objetos. Veja a documentação do log para mais detalhes.
4.2DSN (Data Source Name)
Para definir uma conexão com uma fonte de dados, você deve escrever uma string de caracteres que contenha todos os parâmetros.
Vários formatos são possíveis:
-
Variável de ambiente
É possível indicar o nome de uma variável de ambiente.
env://VAR_NAME
Essa variável deve estar corretamente definida, e seu conteúdo deve ser um DSN suportado pelo Temma (veja os casos abaixo).
-
Banco de dados relacional
protocol://user:password@host[:port][/db_name]- protocol: Tipo de banco de dados, conforme definido pelo PDO (mysql, pgsql, cubrid, sybase, mssql, dblib, firebird, ibm, informix, sqlsrv, oci, odbc, 4D)
- user: Nome de usuário para se conectar ao servidor
- password: Senha usada para se conectar ao servidor
- host: Nome da máquina que hospeda o servidor de banco de dados
- port: Número da porta de conexão de rede (opcional, usa o número de porta padrão)
- db_name: Nome da instância do banco à qual se conectar (opcional)
- mysql://db_user:db_password@localhost/app_db
- pgsql://db_user:db_password@db_server.mydomain.com:3307/app_db
É possível se conectar a um servidor MySQL usando um socket Unix local, em vez de um socket de rede: mysql://user:password@localhost/db_name#/path/to/unix/socket
Em comparação com uma configuração MySQL clássica, você deve acrescentar um caractere de cerquilha (#), seguido do caminho para o arquivo do socket Unix. O nome da máquina host deve ser localhost. Não pode haver número de porta.
Exemplo: mysql://db_user:db_password@localhost/app_db#/var/run/mysqld/mysqld.sock
-
Banco de dados relacional local (SQLite)
sqlite:/path/to/file.sq3
-
Banco de dados não relacional (Redis)
redis://host[:port][/db_number]- host: Nome da máquina que hospeda o servidor Redis
- port: Número da porta de conexão de rede (opcional, usa a porta 6379 por padrão)
- db_number: Número do banco ao qual se conectar (opcional, usa o banco 0 por padrão)
- redis://localhost
- redis://db_server.mydomain.com:6380/2
É possível se conectar a um servidor Redis usando um socket Unix local, em vez de um socket de rede:
redis-sock:///path/to/unix/socket[#base]
Em comparação com uma configuração Redis clássica, você deve indicar o caminho para o arquivo do socket Unix, em vez do nome do servidor. Se um número de banco for especificado, ele deve ser indicado no final, após um caractere de cerquilha (#); caso contrário, o banco 0 é usado por padrão.
Exemplos:- redis-sock:///var/run/redis/redis-server.sock
- redis-sock:///var/run/redis/redis-server.sock#2
-
Servidor de cache (Memcached)
memcache://host[:port]- host: Nome da máquina que hospeda o servidor Memcached
- port: Número da porta de conexão de rede (opcional, usa a porta 11211 por padrão)
É possível configurar o cliente para se conectar a vários servidores Memcached (a distribuição dos dados entre vários servidores Memcached é gerenciada do lado do cliente). Para isso, você deve indicar os servidores um após o outro, separados por ponto e vírgula (;); cada servidor pode vir acompanhado de um número de porta específico.
Exemplo: memcache://localhost;serv1:11212;serv2
É possível se conectar a um servidor Memcached usando um socket Unix local, em vez de um socket de rede:
memcache:///path/to/unix/socket
Em comparação com uma configuração Memcached clássica, você deve indicar o caminho para o arquivo do socket Unix, em vez do nome do servidor.
Exemplo: memcache:///var/run/memcached/memcached.sock
4.3loglevels
A seção loglevels é usada para definir os diferentes níveis de log. Você encontrará informações sobre seu uso em a página de documentação dedicada ao log.
Você pode definir várias "classes" de log, cada uma com um limite de exibição diferente. O limite determina as mensagens que aparecerão no arquivo etc/temma.log. Quando um código tenta gravar uma mensagem de log, essa mensagem só aparecerá se seu nível de criticidade for maior ou igual ao do limite correspondente.
Os diferentes valores de limite possíveis são:
- DEBUG: mensagem de depuração (criticidade mais baixa)
- INFO: mensagem de informação (nível padrão para mensagens cujo nível não é especificado)
- NOTE: notificação; mensagem normal, mas significativa (limite padrão)
- WARN: mensagem de alerta; a aplicação não funciona normalmente, mas pode continuar funcionando.
- ERROR: mensagem de erro; a aplicação não está funcionando normalmente e deve parar.
- CRIT: mensagem de erro crítico; a aplicação corre o risco de danificar seu ambiente (sistema de arquivos ou banco de dados).
Você pode definir suas próprias "classes" de log, de acordo com as necessidades da sua aplicação. Além disso, você pode usar as seguintes classes:
- Temma/Base: Logs referentes aos objetos base do Temma (banco de dados, autoloader, sessões, etc.).
- Temma/Web: Logs referentes aos objetos do próprio framework.
- default: Usada para definir o limite padrão, para todas as classes que não estão definidas.
Se a diretiva loglevels não contiver um array associativo, mas apenas uma string representando um limite de log, ela será usada para todas as mensagens, independentemente de sua "classe" específica.
4.4bufferingLoglevels
A seção bufferingLoglevels não é necessariamente obrigatória. Ela é usada para modificar o comportamento do log.
Como visto anteriormente (veja a seção loglevels), uma mensagem aparece no arquivo de log se seu nível de criticidade for maior ou igual ao limite definido. Caso contrário, ela é simplesmente descartada.
Mas às vezes você quer um comportamento diferente: que as mensagens que não são gravadas sejam armazenadas, e que, se uma mensagem acabar sendo gravada, todas as mensagens pendentes sejam gravadas primeiro no arquivo de log.
Com a diretiva bufferingLoglevels, torna-se possível definir, para cada "classe" de log, o limite acima do qual as mensagens não gravadas são armazenadas para depois. Ela é usada da mesma forma que loglevels, exceto que as mensagens relacionadas a uma classe não listada não serão armazenadas.
4.5routes
As rotas são usadas para definir "controladores virtuais", que servem como aliases para controladores reais. Isso é particularmente útil para permitir o acesso a controladores por nomes de arquivo (como os clássicos robots.txt e sitemap.xml). Isso também possibilita ter o mesmo controlador respondendo em várias URLs diferentes, possivelmente realizando processamentos diferentes de acordo com o nome do controlador solicitado.
4.6plugins
Os plugins são usados para executar código antes e/ou depois da execução do controlador. Você encontrará informações detalhadas em a página de documentação dedicada.
A variável _pre é usada para listar os plugins que serão executados antes dos controladores.
A variável _post é usada para listar os plugins que serão executados depois dos controladores.
Os controladores podem ser listados pelo nome, especificando diretivas _pre e _post específicas para cada um. Também é possível especificar plugins para uma ação particular de um controlador.
4.7errorPages
Em alguns casos, seu site precisará enviar um código de erro HTTP. E se ocorrer um erro no seu próprio código (devido a uma consulta SQL incorreta, por exemplo), o framework decidirá enviar um erro 500.
É possível exibir uma página HTML estática diferente para cada tipo de erro HTTP, e definir uma página padrão para todos os tipos de erro que não sejam explicitamente configurados.
Para não definir todas as páginas de erro possíveis, você pode usar a chave "default" para definir a página padrão.
Se você quiser usar a mesma página, independentemente do erro, pode simplesmente fornecer uma string, e não um array associativo.
4.8includePaths
Essa seção é usada para adicionar caminhos de inclusão nos quais o PHP pode buscar os objetos a serem incluídos.
Ao contrário da diretiva phpIncludePaths, os caminhos definidos com essa diretiva só são válidos ao carregar objetos com o autoloader.
Basicamente, o Temma adiciona o diretório lib/ do projeto ao caminho de inclusão.
Usando o autoloader, se você escrever new \Aa\Bb(); o PHP tentará os seguintes caminhos:
- /path/to/lib1/Aa/Bb.php
- /path2/Aa/Bb.php
- lib/Aa/Bb.php
Pode haver momentos em que você precise especificar caminhos de inclusão específicos para determinados prefixos de namespace. Digamos que seu arquivo etc/temma.php contenha a seguinte configuração:
<?php
return [
'includePaths' => [
'\Acme\Log' => '/path/to/acme-log/lib',
'\Aa\\Bb' => '/path/to/Bb',
]
];
Se você escrever o código new \Acme\Log\Writer();
O PHP tentará carregar o arquivo /path/to/acme-log/lib/Writer.php
Por outro lado, se você escrever new \Aa\Bb\Cc();
O PHP carregará o arquivo /path/to/Bb/Cc.php
É possível especificar caminhos de inclusão com e sem namespace ao mesmo tempo:
<?php
return [
'includePaths' => [
'/path/to/lib1',
'/path2',
'\Acme\Log' => '/path/to/acme-log/lib',
'\Aa\\Bb' => '/path/to/Bb',
]
];
4.9phpIncludePaths
Essa seção é usada para adicionar caminhos de inclusão nos quais o PHP pode buscar arquivos a serem incluídos.
Basicamente, o Temma adiciona o diretório lib/ do projeto ao caminho de inclusão.
Assim, se você escrever no seu código: include('Toto.php');
O arquivo lib/Toto.php será carregado.
Mas se você adicionar a seguinte configuração no seu arquivo etc/temma.php:
<?php
return [
'includePaths' => [
'/path/to/lib1',
'/path2',
]
];
Se você escrever no seu código require_once('foo.php');
O PHP tentará carregar sucessivamente vários caminhos, até encontrar o arquivo. Na ordem, ele tentará:
- /path/to/lib1/foo.php
- /path2/foo.php
- lib/foo.php
4.10autoimport
Todas as diretivas colocadas na seção autoimport são carregadas automaticamente dentro da variável de template $conf.
As variáveis de template podem ser acessadas nos controladores escrevendo $this['variable'].
Elas também podem ser criadas (ou sobrescritas) escrevendo $this['variable'] = $ value;.
4.11validationTypes
Essa seção é usada para declarar tipos que podem ser usados na validação de dados. Contratos de validação (veja a documentação do objeto DataFilter) ou objetos de validação podem ser associados a esses tipos.
Os tipos definidos podem então ser usados nos métodos de validação do objeto Request, nos atributos de validação, ou no objeto DataFilter.
5Configuração estendida
O arquivo de configuração pode conter seções de configuração estendidas. O objetivo delas é agrupar variáveis de parametrização adicionais, classificando-as por funcionalidade. Assim, um controlador ou um plugin pode facilmente recuperar os valores que lhe interessam, sem precisar conhecer todas as variáveis de configuração existentes.
Para recuperar uma seção inteira de configuração estendida, você precisa passar pelo objeto de configuração, que está disponível diretamente nos controladores:
$this->_config
E por meio do componente de injeção de dependências:
$this->_loader->config
Por exemplo, para recuperar toda a configuração estendida x-homepage, você deve escrever:
$data = $this->_config->xtra('homepage');
Para recuperar a variável senderName da seção x-email:
$data = $this->_config->xtra('email', 'senderName');
Também é possível especificar um valor padrão que será usado caso a variável solicitada não exista:
$data = $this->_config->xtra('email', 'recipient', 'contact@host.domain');
6Configuração por plataforma e sobrescrita de configuração
Em vez de ter um único arquivo etc/temma.php (ou etc/temma.json, etc.), você pode usar arquivos diferentes de acordo com a plataforma em que o site é implantado: local, teste, staging, produção...
Para isso, você precisa definir uma variável de ambiente ENVIRONMENT, cujo valor é o nome da plataforma atual.
Por exemplo, você poderia ter um arquivo etc/temma.test.php e um arquivo etc/temma.prod.php. O primeiro será usado em servidores cuja variável de ambiente ENVIRONMENT estiver definida como test, e o segundo em servidores cuja variável estiver definida como prod.
Muitas vezes, apenas certas partes da configuração diferem de uma plataforma para outra. Nesse caso, você pode colocar toda a
configuração comum no arquivo usual etc/temma.php, e colocar as configurações específicas de cada plataforma
nos arquivos específicos.
O arquivo geral é lido primeiro, e o arquivo específico da plataforma o sobrescreve com os valores que contém.
Além disso, os arquivos não precisam estar todos no mesmo formato. Você pode ter um arquivo geral etc/temma.php, e sobrescrevê-lo em staging com um arquivo etc/temma.staging.json e em produção com um arquivo etc/temma.prod.yaml.
Por fim, pode ser necessário sobrescrever o arquivo de configuração adicionando itens no início de uma lista definida (por exemplo,
para adicionar um plugin no início da lista de pré-plugins). Nesse caso, acrescente o sufixo __prepend (começando
com dois underscores) à chave de configuração.
Por exemplo:
<?php
return [
'plugins' => [
'_pre__prepend' => [
// esse plugin será o primeiro da lista '_pre'
'MyFirstPlugin'
]
]
];