Deprecated : Constant E_STRICT is deprecated in /home/pastorz/old-espace-client/vendor/symfony/error-handler/ErrorHandler.php on line 58
Deprecated : Constant E_STRICT is deprecated in /home/pastorz/old-espace-client/vendor/symfony/error-handler/ErrorHandler.php on line 76
Symfony Profiler
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\MonologBundle\DependencyInjection ;
use Monolog\Logger ;
use Symfony\Component\Config\Definition\BaseNode ;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition ;
use Symfony\Component\Config\Definition\Builder\TreeBuilder ;
use Symfony\Component\Config\Definition\ConfigurationInterface ;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException ;
/**
* This class contains the configuration information for the bundle
*
* This information is solely responsible for how the different configuration
* sections are normalized, and merged.
*
* Possible handler types and related configurations (brackets indicate optional params):
*
* - service:
* - id
*
* - stream:
* - path: string
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
* - [file_permission]: int|null, defaults to null (0644)
* - [use_locking]: bool, defaults to false
*
* - console:
* - [verbosity_levels]: level => verbosity configuration
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
* - [console_formatter_options]: array
*
* - firephp:
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - browser_console:
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - gelf:
* - publisher: {id: ...} or {hostname: ..., port: ..., chunk_size: ...}
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - chromephp:
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - rotating_file:
* - path: string
* - [max_files]: files to keep, defaults to zero (infinite)
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
* - [file_permission]: string|null, defaults to null
* - [use_locking]: bool, defaults to false
* - [filename_format]: string, defaults to '{filename}-{date}'
* - [date_format]: string, defaults to 'Y-m-d'
*
* - mongo:
* - mongo:
* - id: optional if host is given
* - host: database host name, optional if id is given
* - [port]: defaults to 27017
* - [user]: database user name
* - pass: mandatory only if user is present
* - [database]: defaults to monolog
* - [collection]: defaults to logs
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - elastic_search:
* - elasticsearch:
* - id: optional if host is given
* - host: elastic search host name, with scheme (e.g. "https://127.0.0.1:9200")
* - [user]: elastic search user name
* - [password]: elastic search user password
* - [index]: index name, defaults to monolog
* - [document_type]: document_type, defaults to logs
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - elastica:
* - elasticsearch:
* - id: optional if host is given
* - host: elastic search host name. Do not prepend with http(s)://
* - [port]: defaults to 9200
* - [transport]: transport protocol (http by default)
* - [user]: elastic search user name
* - [password]: elastic search user password
* - [index]: index name, defaults to monolog
* - [document_type]: document_type, defaults to logs
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - redis:
* - redis:
* - id: optional if host is given
* - host: 127.0.0.1
* - password: null
* - port: 6379
* - database: 0
* - key_name: monolog_redis
*
* - predis:
* - redis:
* - id: optional if host is given
* - host: tcp://10.0.0.1:6379
* - key_name: monolog_redis
*
* - fingers_crossed:
* - handler: the wrapped handler's name
* - [action_level|activation_strategy]: minimum level or service id to activate the handler, defaults to WARNING
* - [excluded_404s]: if set, the strategy will be changed to one that excludes 404s coming from URLs matching any of those patterns
* - [excluded_http_codes]: if set, the strategy will be changed to one that excludes specific HTTP codes (requires Symfony Monolog bridge 4.1+)
* - [buffer_size]: defaults to 0 (unlimited)
* - [stop_buffering]: bool to disable buffering once the handler has been activated, defaults to true
* - [passthru_level]: level name or int value for messages to always flush, disabled by default
* - [bubble]: bool, defaults to true
*
* - filter:
* - handler: the wrapped handler's name
* - [accepted_levels]: list of levels to accept
* - [min_level]: minimum level to accept (only used if accepted_levels not specified)
* - [max_level]: maximum level to accept (only used if accepted_levels not specified)
* - [bubble]: bool, defaults to true
*
* - buffer:
* - handler: the wrapped handler's name
* - [buffer_size]: defaults to 0 (unlimited)
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
* - [flush_on_overflow]: bool, defaults to false
*
* - deduplication:
* - handler: the wrapped handler's name
* - [store]: The file/path where the deduplication log should be kept, defaults to %kernel.cache_dir%/monolog_dedup_*
* - [deduplication_level]: The minimum logging level for log records to be looked at for deduplication purposes, defaults to ERROR
* - [time]: The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through, defaults to 60
* - [bubble]: bool, defaults to true
*
* - group:
* - members: the wrapped handlers by name
* - [bubble]: bool, defaults to true
*
* - whatfailuregroup:
* - members: the wrapped handlers by name
* - [bubble]: bool, defaults to true
*
* - fallbackgroup
* - members: the wrapped handlers by name
* - [bubble]: bool, defaults to true
*
* - syslog:
* - ident: string
* - [facility]: defaults to 'user', use any of the LOG_* facility constant but without LOG_ prefix, e.g. user for LOG_USER
* - [logopts]: defaults to LOG_PID
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - syslogudp:
* - host: syslogd host name
* - [port]: defaults to 514
* - [facility]: defaults to 'user', use any of the LOG_* facility constant but without LOG_ prefix, e.g. user for LOG_USER
* - [logopts]: defaults to LOG_PID
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
* - [ident]: string, defaults to
*
* - swift_mailer:
* - from_email: optional if email_prototype is given
* - to_email: optional if email_prototype is given
* - subject: optional if email_prototype is given
* - [email_prototype]: service id of a message, defaults to a default message with the three fields above
* - [content_type]: optional if email_prototype is given, defaults to text/plain
* - [mailer]: mailer service, defaults to mailer
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
* - [lazy]: use service lazy loading, bool, defaults to true
*
* - native_mailer:
* - from_email: string
* - to_email: string
* - subject: string
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
* - [headers]: optional array containing additional headers: ['Foo: Bar', '...']
*
* - symfony_mailer:
* - from_email: optional if email_prototype is given
* - to_email: optional if email_prototype is given
* - subject: optional if email_prototype is given
* - [email_prototype]: service id of a message, defaults to a default message with the three fields above
* - [mailer]: mailer service id, defaults to mailer.mailer
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - socket:
* - connection_string: string
* - [timeout]: float
* - [connection_timeout]: float
* - [persistent]: bool
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - pushover:
* - token: pushover api token
* - user: user id or array of ids
* - [title]: optional title for messages, defaults to the server hostname
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
* - [timeout]: float
* - [connection_timeout]: float
*
* - raven / sentry:
* - dsn: connection string
* - client_id: Raven client custom service id (optional)
* - [release]: release number of the application that will be attached to logs, defaults to null
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
* - [auto_log_stacks]: bool, defaults to false
* - [environment]: string, default to null (no env specified)
*
* - sentry:
* - hub_id: Sentry hub custom service id (optional)
* - [fill_extra_context]: bool, defaults to false
*
* - newrelic:
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
* - [app_name]: new relic app name, default null
*
* - hipchat:
* - token: hipchat api token
* - room: room id or name
* - [notify]: defaults to false
* - [nickname]: defaults to Monolog
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
* - [use_ssl]: bool, defaults to true
* - [message_format]: text or html, defaults to text
* - [host]: defaults to "api.hipchat.com"
* - [api_version]: defaults to "v1"
* - [timeout]: float
* - [connection_timeout]: float
*
* - slack:
* - token: slack api token
* - channel: channel name (with starting #)
* - [bot_name]: defaults to Monolog
* - [icon_emoji]: defaults to null
* - [use_attachment]: bool, defaults to true
* - [use_short_attachment]: bool, defaults to false
* - [include_extra]: bool, defaults to false
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
* - [timeout]: float
* - [connection_timeout]: float
*
* - slackwebhook:
* - webhook_url: slack webhook URL
* - channel: channel name (with starting #)
* - [bot_name]: defaults to Monolog
* - [icon_emoji]: defaults to null
* - [use_attachment]: bool, defaults to true
* - [use_short_attachment]: bool, defaults to false
* - [include_extra]: bool, defaults to false
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - slackbot:
* - team: slack team slug
* - token: slackbot token
* - channel: channel name (with starting #)
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - cube:
* - url: http/udp url to the cube server
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - amqp:
* - exchange: service id of an AMQPExchange
* - [exchange_name]: string, defaults to log
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - error_log:
* - [message_type]: int 0 or 4, defaults to 0
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - null:
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - test:
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - debug:
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - loggly:
* - token: loggly api token
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
* - [tags]: tag names
*
* - logentries:
* - token: logentries api token
* - [use_ssl]: whether or not SSL encryption should be used, defaults to true
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
* - [timeout]: float
* - [connection_timeout]: float
*
* - insightops:
* - token: Log token supplied by InsightOps
* - region: Region where InsightOps account is hosted. Could be 'us' or 'eu'. Defaults to 'us'
* - [use_ssl]: whether or not SSL encryption should be used, defaults to true
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - flowdock:
* - token: flowdock api token
* - source: human readable identifier of the application
* - from_email: email address of the message sender
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - rollbar:
* - id: RollbarNotifier service (mandatory if token is not provided)
* - token: rollbar api token (skip if you provide a RollbarNotifier service id)
* - [config]: config values from https://github.com/rollbar/rollbar-php#configuration-reference
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - server_log:
* - host: server log host. ex: 127.0.0.1:9911
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
*
* - telegram:
* - token: Telegram bot access token provided by BotFather
* - channel: Telegram channel name
* - [level]: level name or int value, defaults to DEBUG
* - [bubble]: bool, defaults to true
* - [parse_mode]: optional the kind of formatting that is used for the message
* - [disable_webpage_preview]: bool, defaults to false, disables link previews for links in the message
* - [disable_notification]: bool, defaults to false, sends the message silently. Users will receive a notification with no sound
* - [split_long_messages]: bool, defaults to false, split messages longer than 4096 bytes into multiple messages
* - [delay_between_messages]: bool, defaults to false, adds a 1sec delay/sleep between sending split messages
*
* - sampling:
* - handler: the wrapped handler's name
* - factor: the sampling factor (e.g. 10 means every ~10th record is sampled)
*
* All handlers can also be marked with `nested: true` to make sure they are never added explicitly to the stack
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Christophe Coevoet <stof@notk.org>
*
* @finalsince 3.9.0
*/
class Configuration implements ConfigurationInterface
{
/**
* Generates the configuration tree builder.
*/
public function getConfigTreeBuilder (): TreeBuilder
{
$treeBuilder = new TreeBuilder ( 'monolog' );
$rootNode = $treeBuilder -> getRootNode ();
$handlers = $rootNode
-> fixXmlConfig ( 'channel' )
->fixXmlConfig ( 'handler' )
->children ()
->scalarNode ( 'use_microseconds' )-> defaultTrue ()-> end ()
->arrayNode ( 'channels' )
->canBeUnset ()
->prototype ( 'scalar' )-> end ()
->end ()
->arrayNode ( 'handlers' );
$handlers
-> canBeUnset ()
->useAttributeAsKey ( 'name' )
->validate ()
->ifTrue (function ( $v ) { return isset( $v [ 'debug' ]); })
->thenInvalid ( 'The "debug" name cannot be used as it is reserved for the handler of the profiler' )
->end ()
->example ([
'syslog' => [
'type' => 'stream' ,
'path' => '/var/log/symfony.log' ,
'level' => 'ERROR' ,
'bubble' => 'false' ,
'formatter' => 'my_formatter' ,
],
'main' => [
'type' => 'fingers_crossed' ,
'action_level' => 'WARNING' ,
'buffer_size' => 30 ,
'handler' => 'custom' ,
],
'custom' => [
'type' => 'service' ,
'id' => 'my_handler' ,
]
]);
$handlerNode = $handlers
-> prototype ( 'array' )
->fixXmlConfig ( 'member' )
->fixXmlConfig ( 'excluded_404' )
->fixXmlConfig ( 'excluded_http_code' )
->fixXmlConfig ( 'tag' )
->fixXmlConfig ( 'accepted_level' )
->fixXmlConfig ( 'header' )
->canBeUnset ();
$handlerNode
-> children ()
->scalarNode ( 'type' )
->isRequired ()
->treatNullLike ( 'null' )
->beforeNormalization ()
->always ()
->then (function ( $v ) { return strtolower ( $v ); })
->end ()
->end ()
->scalarNode ( 'id' )-> end () // service & rollbar
-> scalarNode ( 'priority' )-> defaultValue ( 0 )-> end ()
->scalarNode ( 'level' )-> defaultValue ( 'DEBUG' )-> end ()
->booleanNode ( 'bubble' )-> defaultTrue ()-> end ()
->scalarNode ( 'app_name' )-> defaultNull ()-> end ()
->booleanNode ( 'fill_extra_context' )-> defaultFalse ()-> end () // sentry
-> booleanNode ( 'include_stacktraces' )-> defaultFalse ()-> end ()
->arrayNode ( 'process_psr_3_messages' )
->addDefaultsIfNotSet ()
->beforeNormalization ()
->ifTrue (static function ( $v ) { return ! \is_array ( $v ); })
->then (static function ( $v ) { return [ 'enabled' => $v ]; })
->end ()
->children ()
->booleanNode ( 'enabled' )-> defaultNull ()-> end ()
->scalarNode ( 'date_format' )-> end ()
->booleanNode ( 'remove_used_context_fields' )-> end ()
->end ()
->end ()
->scalarNode ( 'path' )-> defaultValue ( '%kernel.logs_dir%/%kernel.environment%.log' )-> end () // stream and rotating
-> scalarNode ( 'file_permission' ) // stream and rotating
-> defaultNull ()
->beforeNormalization ()
->ifString ()
->then (function ( $v ) {
if (substr ( $v , 0 , 1 ) === '0' ) {
return octdec ( $v );
}
return (int) $v ;
})
->end ()
->end ()
->booleanNode ( 'use_locking' )-> defaultFalse ()-> end () // stream and rotating
-> scalarNode ( 'filename_format' )-> defaultValue ( '{filename}-{date}' )-> end () //rotating
-> scalarNode ( 'date_format' )-> defaultValue ( 'Y-m-d' )-> end () //rotating
-> scalarNode ( 'ident' )-> defaultFalse ()-> end () // syslog and syslogudp
-> scalarNode ( 'logopts' )-> defaultValue ( LOG_PID )-> end () // syslog
-> scalarNode ( 'facility' )-> defaultValue ( 'user' )-> end () // syslog
-> scalarNode ( 'max_files' )-> defaultValue ( 0 )-> end () // rotating
-> scalarNode ( 'action_level' )-> defaultValue ( 'WARNING' )-> end () // fingers_crossed
-> scalarNode ( 'activation_strategy' )-> defaultNull ()-> end () // fingers_crossed
-> booleanNode ( 'stop_buffering' )-> defaultTrue ()-> end () // fingers_crossed
-> scalarNode ( 'passthru_level' )-> defaultNull ()-> end () // fingers_crossed
-> arrayNode ( 'excluded_404s' ) // fingers_crossed
-> canBeUnset ()
->prototype ( 'scalar' )-> end ()
->end ()
->arrayNode ( 'excluded_http_codes' ) // fingers_crossed
-> canBeUnset ()
->beforeNormalization ()
->always (function ( $values ) {
return array_map (function ( $value ) {
/*
* Allows YAML:
* excluded_http_codes: [403, 404, { 400: ['^/foo', '^/bar'] }]
*
* and XML:
* <monolog:excluded-http-code code="403">
* <monolog:url>^/foo</monolog:url>
* <monolog:url>^/bar</monolog:url>
* </monolog:excluded-http-code>
* <monolog:excluded-http-code code="404" />
*/
if ( is_array ( $value )) {
return isset($value [ 'code' ]) ? $value : [ 'code' => key ( $value ), 'urls' => current ( $value )];
}
return ['code' => $value , 'urls' => []];
}, $values );
})
->end ()
->prototype ( 'array' )
->children ()
->scalarNode ( 'code' )-> end ()
->arrayNode ( 'urls' )
->prototype ( 'scalar' )-> end ()
->end ()
->end ()
->end ()
->end ()
->arrayNode ( 'accepted_levels' ) // filter
-> canBeUnset ()
->prototype ( 'scalar' )-> end ()
->end ()
->scalarNode ( 'min_level' )-> defaultValue ( 'DEBUG' )-> end () // filter
-> scalarNode ( 'max_level' )-> defaultValue ( 'EMERGENCY' )-> end () //filter
-> scalarNode ( 'buffer_size' )-> defaultValue ( 0 )-> end () // fingers_crossed and buffer
-> booleanNode ( 'flush_on_overflow' )-> defaultFalse ()-> end () // buffer
-> scalarNode ( 'handler' )-> end () // fingers_crossed and buffer
-> scalarNode ( 'url' )-> end () // cube
-> scalarNode ( 'exchange' )-> end () // amqp
-> scalarNode ( 'exchange_name' )-> defaultValue ( 'log' )-> end () // amqp
-> scalarNode ( 'room' )-> end () // hipchat
-> scalarNode ( 'message_format' )-> defaultValue ( 'text' )-> end () // hipchat
-> scalarNode ( 'api_version' )-> defaultNull ()-> end () // hipchat
-> scalarNode ( 'channel' )-> defaultNull ()-> end () // slack & slackwebhook & slackbot & telegram
-> scalarNode ( 'bot_name' )-> defaultValue ( 'Monolog' )-> end () // slack & slackwebhook
-> scalarNode ( 'use_attachment' )-> defaultTrue ()-> end () // slack & slackwebhook
-> scalarNode ( 'use_short_attachment' )-> defaultFalse ()-> end () // slack & slackwebhook
-> scalarNode ( 'include_extra' )-> defaultFalse ()-> end () // slack & slackwebhook
-> scalarNode ( 'icon_emoji' )-> defaultNull ()-> end () // slack & slackwebhook
-> scalarNode ( 'webhook_url' )-> end () // slackwebhook
-> scalarNode ( 'team' )-> end () // slackbot
-> scalarNode ( 'notify' )-> defaultFalse ()-> end () // hipchat
-> scalarNode ( 'nickname' )-> defaultValue ( 'Monolog' )-> end () // hipchat
-> scalarNode ( 'token' )-> end () // pushover & hipchat & loggly & logentries & flowdock & rollbar & slack & slackbot & insightops & telegram
-> scalarNode ( 'region' )-> end () // insightops
-> scalarNode ( 'source' )-> end () // flowdock
-> booleanNode ( 'use_ssl' )-> defaultTrue ()-> end () // logentries & hipchat & insightops
-> variableNode ( 'user' ) // pushover
-> validate ()
->ifTrue (function ( $v ) {
return !is_string ( $v ) && ! is_array ( $v );
})
->thenInvalid ( 'User must be a string or an array.' )
->end ()
->end ()
->scalarNode ( 'title' )-> defaultNull ()-> end () // pushover
-> scalarNode ( 'host' )-> defaultNull ()-> end () // syslogudp & hipchat
-> scalarNode ( 'port' )-> defaultValue ( 514 )-> end () // syslogudp
-> arrayNode ( 'config' )
->canBeUnset ()
->prototype ( 'scalar' )-> end ()
->end () // rollbar
-> arrayNode ( 'members' ) // group, whatfailuregroup, fallbackgroup
-> canBeUnset ()
->performNoDeepMerging ()
->prototype ( 'scalar' )-> end ()
->end ()
->scalarNode ( 'connection_string' )-> end () // socket_handler
-> scalarNode ( 'timeout' )-> end () // socket_handler, logentries, pushover, hipchat & slack
-> scalarNode ( 'time' )-> defaultValue ( 60 )-> end () // deduplication
-> scalarNode ( 'deduplication_level' )-> defaultValue ( Logger :: ERROR )-> end () // deduplication
-> scalarNode ( 'store' )-> defaultNull ()-> end () // deduplication
-> scalarNode ( 'connection_timeout' )-> end () // socket_handler, logentries, pushover, hipchat & slack
-> booleanNode ( 'persistent' )-> end () // socket_handler
-> scalarNode ( 'dsn' )-> end () // raven_handler, sentry_handler
-> scalarNode ( 'hub_id' )-> defaultNull ()-> end () // sentry_handler
-> scalarNode ( 'client_id' )-> defaultNull ()-> end () // raven_handler, sentry_handler
-> scalarNode ( 'auto_log_stacks' )-> defaultFalse ()-> end () // raven_handler
-> scalarNode ( 'release' )-> defaultNull ()-> end () // raven_handler, sentry_handler
-> scalarNode ( 'environment' )-> defaultNull ()-> end () // raven_handler, sentry_handler
-> scalarNode ( 'message_type' )-> defaultValue ( 0 )-> end () // error_log
-> scalarNode ( 'parse_mode' )-> defaultNull ()-> end () // telegram
-> booleanNode ( 'disable_webpage_preview' )-> defaultNull ()-> end () // telegram
-> booleanNode ( 'disable_notification' )-> defaultNull ()-> end () // telegram
-> booleanNode ( 'split_long_messages' )-> defaultFalse ()-> end () // telegram
-> booleanNode ( 'delay_between_messages' )-> defaultFalse ()-> end () // telegram
-> integerNode ( 'factor' )-> defaultValue ( 1 )-> min ( 1 )-> end () // sampling
-> arrayNode ( 'tags' ) // loggly
-> beforeNormalization ()
->ifString ()
->then (function ( $v ) { return explode ( ',' , $v ); })
->end ()
->beforeNormalization ()
->ifArray ()
->then (function ( $v ) { return array_filter ( array_map ( 'trim' , $v )); })
->end ()
->prototype ( 'scalar' )-> end ()
->end ()
// console
-> variableNode ( 'console_formater_options' )
->setDeprecated ( 'symfony/monolog-bundle' , 3.7 , '"%path%.%node%" is deprecated, use "%path%.console_formatter_options" instead.' )
->validate ()
->ifTrue (function ( $v ) {
return !is_array ( $v );
})
->thenInvalid ( 'The console_formater_options must be an array.' )
->end ()
->end ()
->variableNode ( 'console_formatter_options' )
->defaultValue ([])
->validate ()
->ifTrue (static function ( $v ) { return ! is_array ( $v ); })
->thenInvalid ( 'The console_formatter_options must be an array.' )
->end ()
->end ()
->scalarNode ( 'formatter' )-> end ()
->booleanNode ( 'nested' )-> defaultFalse ()-> end ()
->end ();
$this -> addGelfSection ( $handlerNode );
$this -> addMongoSection ( $handlerNode );
$this -> addElasticsearchSection ( $handlerNode );
$this -> addRedisSection ( $handlerNode );
$this -> addPredisSection ( $handlerNode );
$this -> addMailerSection ( $handlerNode );
$this -> addVerbosityLevelSection ( $handlerNode );
$this -> addChannelsSection ( $handlerNode );
$handlerNode
-> beforeNormalization ()
->always (static function ( $v ) {
if (empty($v [ 'console_formatter_options' ]) && !empty( $v [ 'console_formater_options' ])) {
$v [ 'console_formatter_options' ] = $v [ 'console_formater_options' ];
}
return $v ;
})
->end ()
->validate ()
->always (static function ( $v ) { unset( $v [ 'console_formater_options' ]); return $v ; })
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'service' === $v [ 'type' ] && !empty( $v [ 'formatter' ]); })
->thenInvalid ( 'Service handlers can not have a formatter configured in the bundle, you must reconfigure the service itself instead' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return ( 'fingers_crossed' === $v [ 'type' ] || 'buffer' === $v [ 'type' ] || 'filter' === $v [ 'type' ] || 'sampling' === $v [ 'type' ]) && empty( $v [ 'handler' ]); })
->thenInvalid ( 'The handler has to be specified to use a FingersCrossedHandler or BufferHandler or FilterHandler or SamplingHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'fingers_crossed' === $v [ 'type' ] && !empty( $v [ 'excluded_404s' ]) && !empty( $v [ 'activation_strategy' ]); })
->thenInvalid ( 'You can not use excluded_404s together with a custom activation_strategy in a FingersCrossedHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'fingers_crossed' === $v [ 'type' ] && !empty( $v [ 'excluded_http_codes' ]) && !empty( $v [ 'activation_strategy' ]); })
->thenInvalid ( 'You can not use excluded_http_codes together with a custom activation_strategy in a FingersCrossedHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'fingers_crossed' === $v [ 'type' ] && !empty( $v [ 'excluded_http_codes' ]) && !empty( $v [ 'excluded_404s' ]); })
->thenInvalid ( 'You can not use excluded_http_codes together with excluded_404s in a FingersCrossedHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'fingers_crossed' !== $v [ 'type' ] && (!empty( $v [ 'excluded_http_codes' ]) || !empty( $v [ 'excluded_404s' ])); })
->thenInvalid ( 'You can only use excluded_http_codes/excluded_404s with a FingersCrossedHandler definition' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'filter' === $v [ 'type' ] && "DEBUG" !== $v [ 'min_level' ] && !empty( $v [ 'accepted_levels' ]); })
->thenInvalid ( 'You can not use min_level together with accepted_levels in a FilterHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'filter' === $v [ 'type' ] && "EMERGENCY" !== $v [ 'max_level' ] && !empty( $v [ 'accepted_levels' ]); })
->thenInvalid ( 'You can not use max_level together with accepted_levels in a FilterHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'rollbar' === $v [ 'type' ] && !empty( $v [ 'id' ]) && !empty( $v [ 'token' ]); })
->thenInvalid ( 'You can not use both an id and a token in a RollbarHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'rollbar' === $v [ 'type' ] && empty( $v [ 'id' ]) && empty( $v [ 'token' ]); })
->thenInvalid ( 'The id or the token has to be specified to use a RollbarHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'telegram' === $v [ 'type' ] && (empty( $v [ 'token' ]) || empty( $v [ 'channel' ])); })
->thenInvalid ( 'The token and channel have to be specified to use a TelegramBotHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'service' === $v [ 'type' ] && !isset( $v [ 'id' ]); })
->thenInvalid ( 'The id has to be specified to use a service as handler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'syslogudp' === $v [ 'type' ] && !isset( $v [ 'host' ]); })
->thenInvalid ( 'The host has to be specified to use a syslogudp as handler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'socket' === $v [ 'type' ] && !isset( $v [ 'connection_string' ]); })
->thenInvalid ( 'The connection_string has to be specified to use a SocketHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'pushover' === $v [ 'type' ] && (empty( $v [ 'token' ]) || empty( $v [ 'user' ])); })
->thenInvalid ( 'The token and user have to be specified to use a PushoverHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'raven' === $v [ 'type' ] && ! array_key_exists ( 'dsn' , $v ) && null === $v [ 'client_id' ]; })
->thenInvalid ( 'The DSN has to be specified to use a RavenHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'sentry' === $v [ 'type' ] && ! array_key_exists ( 'dsn' , $v ) && null === $v [ 'hub_id' ] && null === $v [ 'client_id' ]; })
->thenInvalid ( 'The DSN has to be specified to use Sentry\'s handler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'sentry' === $v [ 'type' ] && null !== $v [ 'hub_id' ] && null !== $v [ 'client_id' ]; })
->thenInvalid ( 'You can not use both a hub_id and a client_id in a Sentry handler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'hipchat' === $v [ 'type' ] && (empty( $v [ 'token' ]) || empty( $v [ 'room' ])); })
->thenInvalid ( 'The token and room have to be specified to use a HipChatHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'hipchat' === $v [ 'type' ] && ! in_array ( $v [ 'message_format' ], [ 'text' , 'html' ]); })
->thenInvalid ( 'The message_format has to be "text" or "html" in a HipChatHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'hipchat' === $v [ 'type' ] && null !== $v [ 'api_version' ] && ! in_array ( $v [ 'api_version' ], [ 'v1' , 'v2' ], true ); })
->thenInvalid ( 'The api_version has to be "v1" or "v2" in a HipChatHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'slack' === $v [ 'type' ] && (empty( $v [ 'token' ]) || empty( $v [ 'channel' ])); })
->thenInvalid ( 'The token and channel have to be specified to use a SlackHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'slackwebhook' === $v [ 'type' ] && (empty( $v [ 'webhook_url' ])); })
->thenInvalid ( 'The webhook_url have to be specified to use a SlackWebhookHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'slackbot' === $v [ 'type' ] && (empty( $v [ 'team' ]) || empty( $v [ 'token' ]) || empty( $v [ 'channel' ])); })
->thenInvalid ( 'The team, token and channel have to be specified to use a SlackbotHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'cube' === $v [ 'type' ] && empty( $v [ 'url' ]); })
->thenInvalid ( 'The url has to be specified to use a CubeHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'amqp' === $v [ 'type' ] && empty( $v [ 'exchange' ]); })
->thenInvalid ( 'The exchange has to be specified to use a AmqpHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'loggly' === $v [ 'type' ] && empty( $v [ 'token' ]); })
->thenInvalid ( 'The token has to be specified to use a LogglyHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'loggly' === $v [ 'type' ] && !empty( $v [ 'tags' ]); })
->then (function ( $v ) {
$invalidTags = preg_grep ( '/^[a-z0-9][a-z0-9\.\-_]*$/i' , $v [ 'tags' ], PREG_GREP_INVERT );
if (!empty($invalidTags )) {
throw new InvalidConfigurationException ( sprintf ( 'The following Loggly tags are invalid: %s.' , implode ( ', ' , $invalidTags )));
}
return $v ;
})
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'logentries' === $v [ 'type' ] && empty( $v [ 'token' ]); })
->thenInvalid ( 'The token has to be specified to use a LogEntriesHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'insightops' === $v [ 'type' ] && empty( $v [ 'token' ]); })
->thenInvalid ( 'The token has to be specified to use a InsightOpsHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'flowdock' === $v [ 'type' ] && empty( $v [ 'token' ]); })
->thenInvalid ( 'The token has to be specified to use a FlowdockHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'flowdock' === $v [ 'type' ] && empty( $v [ 'from_email' ]); })
->thenInvalid ( 'The from_email has to be specified to use a FlowdockHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'flowdock' === $v [ 'type' ] && empty( $v [ 'source' ]); })
->thenInvalid ( 'The source has to be specified to use a FlowdockHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'server_log' === $v [ 'type' ] && empty( $v [ 'host' ]); })
->thenInvalid ( 'The host has to be specified to use a ServerLogHandler' )
->end ()
;
return $treeBuilder ;
}
private function addGelfSection ( ArrayNodeDefinition $handerNode )
{
$handerNode
-> children ()
->arrayNode ( 'publisher' )
->canBeUnset ()
->beforeNormalization ()
->ifString ()
->then (function ( $v ) { return [ 'id' => $v ]; })
->end ()
->children ()
->scalarNode ( 'id' )-> end ()
->scalarNode ( 'hostname' )-> end ()
->scalarNode ( 'port' )-> defaultValue ( 12201 )-> end ()
->scalarNode ( 'chunk_size' )-> defaultValue ( 1420 )-> end ()
->end ()
->validate ()
->ifTrue (function ( $v ) {
return !isset($v [ 'id' ]) && !isset( $v [ 'hostname' ]);
})
->thenInvalid ( 'What must be set is either the hostname or the id.' )
->end ()
->end ()
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'gelf' === $v [ 'type' ] && !isset( $v [ 'publisher' ]); })
->thenInvalid ( 'The publisher has to be specified to use a GelfHandler' )
->end ()
;
}
private function addMongoSection ( ArrayNodeDefinition $handerNode )
{
$handerNode
-> children ()
->arrayNode ( 'mongo' )
->canBeUnset ()
->beforeNormalization ()
->ifString ()
->then (function ( $v ) { return [ 'id' => $v ]; })
->end ()
->children ()
->scalarNode ( 'id' )-> end ()
->scalarNode ( 'host' )-> end ()
->scalarNode ( 'port' )-> defaultValue ( 27017 )-> end ()
->scalarNode ( 'user' )-> end ()
->scalarNode ( 'pass' )-> end ()
->scalarNode ( 'database' )-> defaultValue ( 'monolog' )-> end ()
->scalarNode ( 'collection' )-> defaultValue ( 'logs' )-> end ()
->end ()
->validate ()
->ifTrue (function ( $v ) {
return !isset($v [ 'id' ]) && !isset( $v [ 'host' ]);
})
->thenInvalid ( 'What must be set is either the host or the id.' )
->end ()
->validate ()
->ifTrue (function ( $v ) {
return isset($v [ 'user' ]) && !isset( $v [ 'pass' ]);
})
->thenInvalid ( 'If you set user, you must provide a password.' )
->end ()
->end ()
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'mongo' === $v [ 'type' ] && !isset( $v [ 'mongo' ]); })
->thenInvalid ( 'The mongo configuration has to be specified to use a MongoHandler' )
->end ()
;
}
private function addElasticsearchSection ( ArrayNodeDefinition $handerNode )
{
$handerNode
-> children ()
->arrayNode ( 'elasticsearch' )
->canBeUnset ()
->beforeNormalization ()
->ifString ()
->then (function ( $v ) { return [ 'id' => $v ]; })
->end ()
->children ()
->scalarNode ( 'id' )-> end ()
->scalarNode ( 'host' )-> end ()
->scalarNode ( 'port' )-> defaultValue ( 9200 )-> end ()
->scalarNode ( 'transport' )-> defaultValue ( 'Http' )-> end ()
->scalarNode ( 'user' )-> defaultNull ()-> end ()
->scalarNode ( 'password' )-> defaultNull ()-> end ()
->end ()
->validate ()
->ifTrue (function ( $v ) {
return !isset($v [ 'id' ]) && !isset( $v [ 'host' ]);
})
->thenInvalid ( 'What must be set is either the host or the id.' )
->end ()
->end ()
->scalarNode ( 'index' )-> defaultValue ( 'monolog' )-> end () // elasticsearch & elastic_search & elastica
-> scalarNode ( 'document_type' )-> defaultValue ( 'logs' )-> end () // elasticsearch & elastic_search & elastica
-> scalarNode ( 'ignore_error' )-> defaultValue ( false )-> end () // elasticsearch & elastic_search & elastica
-> end ()
;
}
private function addRedisSection ( ArrayNodeDefinition $handerNode )
{
$handerNode
-> children ()
->arrayNode ( 'redis' )
->canBeUnset ()
->beforeNormalization ()
->ifString ()
->then (function ( $v ) { return [ 'id' => $v ]; })
->end ()
->children ()
->scalarNode ( 'id' )-> end ()
->scalarNode ( 'host' )-> end ()
->scalarNode ( 'password' )-> defaultNull ()-> end ()
->scalarNode ( 'port' )-> defaultValue ( 6379 )-> end ()
->scalarNode ( 'database' )-> defaultValue ( 0 )-> end ()
->scalarNode ( 'key_name' )-> defaultValue ( 'monolog_redis' )-> end ()
->end ()
->validate ()
->ifTrue (function ( $v ) {
return !isset($v [ 'id' ]) && !isset( $v [ 'host' ]);
})
->thenInvalid ( 'What must be set is either the host or the service id of the Redis client.' )
->end ()
->end ()
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'redis' === $v [ 'type' ] && empty( $v [ 'redis' ]); })
->thenInvalid ( 'The host has to be specified to use a RedisLogHandler' )
->end ()
;
}
private function addPredisSection ( ArrayNodeDefinition $handerNode )
{
$handerNode
-> children ()
->arrayNode ( 'predis' )
->canBeUnset ()
->beforeNormalization ()
->ifString ()
->then (function ( $v ) { return [ 'id' => $v ]; })
->end ()
->children ()
->scalarNode ( 'id' )-> end ()
->scalarNode ( 'host' )-> end ()
->end ()
->validate ()
->ifTrue (function ( $v ) {
return !isset($v [ 'id' ]) && !isset( $v [ 'host' ]);
})
->thenInvalid ( 'What must be set is either the host or the service id of the Predis client.' )
->end ()
->end ()
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'predis' === $v [ 'type' ] && empty( $v [ 'redis' ]); })
->thenInvalid ( 'The host has to be specified to use a RedisLogHandler' )
->end ()
;
}
private function addMailerSection ( ArrayNodeDefinition $handerNode )
{
$handerNode
-> children ()
->scalarNode ( 'from_email' )-> end () // swift_mailer, native_mailer, symfony_mailer and flowdock
-> arrayNode ( 'to_email' ) // swift_mailer, native_mailer and symfony_mailer
-> prototype ( 'scalar' )-> end ()
->beforeNormalization ()
->ifString ()
->then (function ( $v ) { return [ $v ]; })
->end ()
->end ()
->scalarNode ( 'subject' )-> end () // swift_mailer, native_mailer and symfony_mailer
-> scalarNode ( 'content_type' )-> defaultNull ()-> end () // swift_mailer and symfony_mailer
-> arrayNode ( 'headers' ) // native_mailer
-> canBeUnset ()
->scalarPrototype ()-> end ()
->end ()
->scalarNode ( 'mailer' )-> defaultNull ()-> end () // swift_mailer and symfony_mailer
-> arrayNode ( 'email_prototype' ) // swift_mailer and symfony_mailer
-> canBeUnset ()
->beforeNormalization ()
->ifString ()
->then (function ( $v ) { return [ 'id' => $v ]; })
->end ()
->children ()
->scalarNode ( 'id' )-> isRequired ()-> end ()
->scalarNode ( 'method' )-> defaultNull ()-> end ()
->end ()
->end ()
->booleanNode ( 'lazy' )-> defaultValue ( true )-> end () // swift_mailer
-> end ()
->validate ()
->ifTrue (function ( $v ) { return 'swift_mailer' === $v [ 'type' ] && empty( $v [ 'email_prototype' ]) && (empty( $v [ 'from_email' ]) || empty( $v [ 'to_email' ]) || empty( $v [ 'subject' ])); })
->thenInvalid ( 'The sender, recipient and subject or an email prototype have to be specified to use a SwiftMailerHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'native_mailer' === $v [ 'type' ] && (empty( $v [ 'from_email' ]) || empty( $v [ 'to_email' ]) || empty( $v [ 'subject' ])); })
->thenInvalid ( 'The sender, recipient and subject have to be specified to use a NativeMailerHandler' )
->end ()
->validate ()
->ifTrue (function ( $v ) { return 'symfony_mailer' === $v [ 'type' ] && empty( $v [ 'email_prototype' ]) && (empty( $v [ 'from_email' ]) || empty( $v [ 'to_email' ]) || empty( $v [ 'subject' ])); })
->thenInvalid ( 'The sender, recipient and subject or an email prototype have to be specified to use the Symfony MailerHandler' )
->end ()
;
}
private function addVerbosityLevelSection ( ArrayNodeDefinition $handerNode )
{
$handerNode
-> children ()
->arrayNode ( 'verbosity_levels' ) // console
-> beforeNormalization ()
->ifArray ()
->then (function ( $v ) {
$map = [];
$verbosities = [ 'VERBOSITY_QUIET' , 'VERBOSITY_NORMAL' , 'VERBOSITY_VERBOSE' , 'VERBOSITY_VERY_VERBOSE' , 'VERBOSITY_DEBUG' ];
// allow numeric indexed array with ascendning verbosity and lowercase names of the constants
foreach ( $v as $verbosity => $level ) {
if (is_int ( $verbosity ) && isset( $verbosities [ $verbosity ])) {
$map [ $verbosities [ $verbosity ]] = strtoupper ( $level );
} else {
$map [ strtoupper ( $verbosity )] = strtoupper ( $level );
}
}
return $map ;
})
->end ()
->children ()
->scalarNode ( 'VERBOSITY_QUIET' )-> defaultValue ( 'ERROR' )-> end ()
->scalarNode ( 'VERBOSITY_NORMAL' )-> defaultValue ( 'WARNING' )-> end ()
->scalarNode ( 'VERBOSITY_VERBOSE' )-> defaultValue ( 'NOTICE' )-> end ()
->scalarNode ( 'VERBOSITY_VERY_VERBOSE' )-> defaultValue ( 'INFO' )-> end ()
->scalarNode ( 'VERBOSITY_DEBUG' )-> defaultValue ( 'DEBUG' )-> end ()
->end ()
->validate ()
->always (function ( $v ) {
$map = [];
foreach ($v as $verbosity => $level ) {
$verbosityConstant = 'Symfony\Component\Console\Output\OutputInterface::' . $verbosity ;
if (!defined ( $verbosityConstant )) {
throw new InvalidConfigurationException ( sprintf (
'The configured verbosity "%s" is invalid as it is not defined in Symfony\Component\Console\Output\OutputInterface.' ,
$verbosity
));
}
try {
if (Logger :: API === 3 ) {
$level = Logger :: toMonologLevel ( $level )-> value ;
} else {
$level = Logger :: toMonologLevel ( is_numeric ( $level ) ? (int) $level : $level );
}
} catch (\Psr\Log\InvalidArgumentException $e ) {
throw new InvalidConfigurationException ( sprintf (
'The configured minimum log level "%s" for verbosity "%s" is invalid as it is not defined in Monolog\Logger.' ,
$level , $verbosity
));
}
$map [ constant ( $verbosityConstant )] = $level ;
}
return $map ;
})
->end ()
->end ()
->end ()
;
}
private function addChannelsSection ( ArrayNodeDefinition $handerNode )
{
$handerNode
-> children ()
->arrayNode ( 'channels' )
->fixXmlConfig ( 'channel' , 'elements' )
->canBeUnset ()
->beforeNormalization ()
->ifString ()
->then (function ( $v ) { return [ 'elements' => [ $v ]]; })
->end ()
->beforeNormalization ()
->ifTrue (function ( $v ) { return is_array ( $v ) && is_numeric ( key ( $v )); })
->then (function ( $v ) { return [ 'elements' => $v ]; })
->end ()
->validate ()
->ifTrue (function ( $v ) { return empty( $v ); })
->thenUnset ()
->end ()
->validate ()
->always (function ( $v ) {
$isExclusive = null ;
if (isset($v [ 'type' ])) {
$isExclusive = 'exclusive' === $v [ 'type' ];
}
$elements = [];
foreach ($v [ 'elements' ] as $element ) {
if (0 === strpos ( $element , '!' )) {
if (false === $isExclusive ) {
throw new InvalidConfigurationException ( 'Cannot combine exclusive/inclusive definitions in channels list.' );
}
$elements [] = substr ( $element , 1 );
$isExclusive = true ;
} else {
if (true === $isExclusive ) {
throw new InvalidConfigurationException ( 'Cannot combine exclusive/inclusive definitions in channels list' );
}
$elements [] = $element ;
$isExclusive = false ;
}
}
if (!count ( $elements )) {
return null ;
}
// de-duplicating $elements here in case the handlers are redefined, see https://github.com/symfony/monolog-bundle/issues/433
return [ 'type' => $isExclusive ? 'exclusive' : 'inclusive' , 'elements' => array_unique ( $elements )];
})
->end ()
->children ()
->scalarNode ( 'type' )
->validate ()
->ifNotInArray ([ 'inclusive' , 'exclusive' ])
->thenInvalid ( 'The type of channels has to be inclusive or exclusive' )
->end ()
->end ()
->arrayNode ( 'elements' )
->prototype ( 'scalar' )-> end ()
->end ()
->end ()
->end ()
->end ()
;
}
}