diff --git a/console/skel/symfony-app/composer.json b/console/skel/symfony-app/composer.json
new file mode 100644
index 0000000..571e163
--- /dev/null
+++ b/console/skel/symfony-app/composer.json
@@ -0,0 +1,84 @@
+{
+ "type": "project",
+ "license": "proprietary",
+ "require": {
+ "php": "^7.2.5",
+ "ext-ctype": "*",
+ "ext-iconv": "*",
+ "sensio/framework-extra-bundle": "^5.1",
+ "symfony/asset": "5.0.*",
+ "symfony/console": "5.0.*",
+ "symfony/dotenv": "5.0.*",
+ "symfony/expression-language": "5.0.*",
+ "symfony/flex": "^1.3.1",
+ "symfony/form": "5.0.*",
+ "symfony/framework-bundle": "5.0.*",
+ "symfony/http-client": "5.0.*",
+ "symfony/intl": "5.0.*",
+ "symfony/mailer": "5.0.*",
+ "symfony/monolog-bundle": "^3.1",
+ "symfony/notifier": "5.0.*",
+ "symfony/orm-pack": "*",
+ "symfony/process": "5.0.*",
+ "symfony/security-bundle": "5.0.*",
+ "symfony/serializer-pack": "*",
+ "symfony/string": "5.0.*",
+ "symfony/translation": "5.0.*",
+ "symfony/twig-pack": "*",
+ "symfony/validator": "5.0.*",
+ "symfony/web-link": "5.0.*",
+ "symfony/yaml": "5.0.*"
+ },
+ "require-dev": {
+ "symfony/debug-pack": "*",
+ "symfony/maker-bundle": "^1.0",
+ "symfony/profiler-pack": "^1.0",
+ "symfony/test-pack": "*"
+ },
+ "config": {
+ "preferred-install": {
+ "*": "dist"
+ },
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "App\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "App\\Tests\\": "tests/"
+ }
+ },
+ "replace": {
+ "paragonie/random_compat": "2.*",
+ "symfony/polyfill-ctype": "*",
+ "symfony/polyfill-iconv": "*",
+ "symfony/polyfill-php72": "*",
+ "symfony/polyfill-php71": "*",
+ "symfony/polyfill-php70": "*",
+ "symfony/polyfill-php56": "*"
+ },
+ "scripts": {
+ "auto-scripts": {
+ "cache:clear": "symfony-cmd",
+ "assets:install %PUBLIC_DIR%": "symfony-cmd"
+ },
+ "post-install-cmd": [
+ "@auto-scripts"
+ ],
+ "post-update-cmd": [
+ "@auto-scripts"
+ ]
+ },
+ "conflict": {
+ "symfony/symfony": "*"
+ },
+ "extra": {
+ "symfony": {
+ "allow-contrib": false,
+ "require": "5.0.*"
+ }
+ }
+}
diff --git a/console/skel/symfony-app/composer.lock b/console/skel/symfony-app/composer.lock
new file mode 100644
index 0000000..4581e91
--- /dev/null
+++ b/console/skel/symfony-app/composer.lock
@@ -0,0 +1,6754 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "803912537d34378bb260a0d2052d39c3",
+ "packages": [
+ {
+ "name": "doctrine/annotations",
+ "version": "v1.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/annotations.git",
+ "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/annotations/zipball/904dca4eb10715b92569fbcd79e201d5c349b6bc",
+ "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/lexer": "1.*",
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "doctrine/cache": "1.*",
+ "phpunit/phpunit": "^7.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.7.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Docblock Annotations Parser",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "annotations",
+ "docblock",
+ "parser"
+ ],
+ "time": "2019-10-01T18:55:10+00:00"
+ },
+ {
+ "name": "doctrine/cache",
+ "version": "1.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/cache.git",
+ "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/cache/zipball/382e7f4db9a12dc6c19431743a2b096041bcdd62",
+ "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62",
+ "shasum": ""
+ },
+ "require": {
+ "php": "~7.1"
+ },
+ "conflict": {
+ "doctrine/common": ">2.2,<2.4"
+ },
+ "require-dev": {
+ "alcaeus/mongo-php-adapter": "^1.1",
+ "doctrine/coding-standard": "^6.0",
+ "mongodb/mongodb": "^1.1",
+ "phpunit/phpunit": "^7.0",
+ "predis/predis": "~1.0"
+ },
+ "suggest": {
+ "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.9.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.",
+ "homepage": "https://www.doctrine-project.org/projects/cache.html",
+ "keywords": [
+ "abstraction",
+ "apcu",
+ "cache",
+ "caching",
+ "couchdb",
+ "memcached",
+ "php",
+ "redis",
+ "xcache"
+ ],
+ "time": "2019-11-29T15:36:20+00:00"
+ },
+ {
+ "name": "doctrine/collections",
+ "version": "1.6.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/collections.git",
+ "reference": "6b1e4b2b66f6d6e49983cebfe23a21b7ccc5b0d7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/collections/zipball/6b1e4b2b66f6d6e49983cebfe23a21b7ccc5b0d7",
+ "reference": "6b1e4b2b66f6d6e49983cebfe23a21b7ccc5b0d7",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^6.0",
+ "phpstan/phpstan-shim": "^0.9.2",
+ "phpunit/phpunit": "^7.0",
+ "vimeo/psalm": "^3.2.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.",
+ "homepage": "https://www.doctrine-project.org/projects/collections.html",
+ "keywords": [
+ "array",
+ "collections",
+ "iterators",
+ "php"
+ ],
+ "time": "2019-11-13T13:07:11+00:00"
+ },
+ {
+ "name": "doctrine/common",
+ "version": "2.12.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/common.git",
+ "reference": "2053eafdf60c2172ee1373d1b9289ba1db7f1fc6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/common/zipball/2053eafdf60c2172ee1373d1b9289ba1db7f1fc6",
+ "reference": "2053eafdf60c2172ee1373d1b9289ba1db7f1fc6",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/annotations": "^1.0",
+ "doctrine/cache": "^1.0",
+ "doctrine/collections": "^1.0",
+ "doctrine/event-manager": "^1.0",
+ "doctrine/inflector": "^1.0",
+ "doctrine/lexer": "^1.0",
+ "doctrine/persistence": "^1.1",
+ "doctrine/reflection": "^1.0",
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^1.0",
+ "phpstan/phpstan": "^0.11",
+ "phpstan/phpstan-phpunit": "^0.11",
+ "phpunit/phpunit": "^7.0",
+ "squizlabs/php_codesniffer": "^3.0",
+ "symfony/phpunit-bridge": "^4.0.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.11.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\": "lib/Doctrine/Common"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ },
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com"
+ }
+ ],
+ "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, persistence interfaces, proxies, event system and much more.",
+ "homepage": "https://www.doctrine-project.org/projects/common.html",
+ "keywords": [
+ "common",
+ "doctrine",
+ "php"
+ ],
+ "time": "2020-01-10T15:49:25+00:00"
+ },
+ {
+ "name": "doctrine/dbal",
+ "version": "v2.10.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/dbal.git",
+ "reference": "c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/dbal/zipball/c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8",
+ "reference": "c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/cache": "^1.0",
+ "doctrine/event-manager": "^1.0",
+ "ext-pdo": "*",
+ "php": "^7.2"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^6.0",
+ "jetbrains/phpstorm-stubs": "^2019.1",
+ "phpstan/phpstan": "^0.11.3",
+ "phpunit/phpunit": "^8.4.1",
+ "symfony/console": "^2.0.5|^3.0|^4.0|^5.0"
+ },
+ "suggest": {
+ "symfony/console": "For helpful console commands such as SQL execution and import of files."
+ },
+ "bin": [
+ "bin/doctrine-dbal"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.10.x-dev",
+ "dev-develop": "3.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\DBAL\\": "lib/Doctrine/DBAL"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ }
+ ],
+ "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.",
+ "homepage": "https://www.doctrine-project.org/projects/dbal.html",
+ "keywords": [
+ "abstraction",
+ "database",
+ "db2",
+ "dbal",
+ "mariadb",
+ "mssql",
+ "mysql",
+ "oci8",
+ "oracle",
+ "pdo",
+ "pgsql",
+ "postgresql",
+ "queryobject",
+ "sasql",
+ "sql",
+ "sqlanywhere",
+ "sqlite",
+ "sqlserver",
+ "sqlsrv"
+ ],
+ "time": "2020-01-04T12:56:21+00:00"
+ },
+ {
+ "name": "doctrine/doctrine-bundle",
+ "version": "2.0.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/DoctrineBundle.git",
+ "reference": "6926771140ee87a823c3b2c72602de9dda4490d3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/6926771140ee87a823c3b2c72602de9dda4490d3",
+ "reference": "6926771140ee87a823c3b2c72602de9dda4490d3",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/dbal": "^2.9.0",
+ "doctrine/persistence": "^1.3.3",
+ "jdorn/sql-formatter": "^1.2.16",
+ "php": "^7.1",
+ "symfony/cache": "^4.3.3|^5.0",
+ "symfony/config": "^4.3.3|^5.0",
+ "symfony/console": "^3.4.30|^4.3.3|^5.0",
+ "symfony/dependency-injection": "^4.3.3|^5.0",
+ "symfony/doctrine-bridge": "^4.3.7|^5.0",
+ "symfony/framework-bundle": "^3.4.30|^4.3.3|^5.0",
+ "symfony/service-contracts": "^1.1.1|^2.0"
+ },
+ "conflict": {
+ "doctrine/orm": "<2.6",
+ "twig/twig": "<1.34|>=2.0,<2.4"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^6.0",
+ "doctrine/orm": "^2.6",
+ "ocramius/proxy-manager": "^2.1",
+ "phpunit/phpunit": "^7.5",
+ "symfony/phpunit-bridge": "^4.2",
+ "symfony/property-info": "^4.3.3|^5.0",
+ "symfony/proxy-manager-bridge": "^3.4|^4.3.3|^5.0",
+ "symfony/twig-bridge": "^3.4.30|^4.3.3|^5.0",
+ "symfony/validator": "^3.4.30|^4.3.3|^5.0",
+ "symfony/web-profiler-bundle": "^3.4.30|^4.3.3|^5.0",
+ "symfony/yaml": "^3.4.30|^4.3.3|^5.0",
+ "twig/twig": "^1.34|^2.12"
+ },
+ "suggest": {
+ "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.",
+ "symfony/web-profiler-bundle": "To use the data collector."
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Bundle\\DoctrineBundle\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "http://symfony.com/contributors"
+ },
+ {
+ "name": "Doctrine Project",
+ "homepage": "http://www.doctrine-project.org/"
+ }
+ ],
+ "description": "Symfony DoctrineBundle",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "database",
+ "dbal",
+ "orm",
+ "persistence"
+ ],
+ "time": "2020-01-18T11:56:15+00:00"
+ },
+ {
+ "name": "doctrine/doctrine-migrations-bundle",
+ "version": "2.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git",
+ "reference": "856437e8de96a70233e1f0cc2352fc8dd15a899d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/856437e8de96a70233e1f0cc2352fc8dd15a899d",
+ "reference": "856437e8de96a70233e1f0cc2352fc8dd15a899d",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/doctrine-bundle": "~1.0|~2.0",
+ "doctrine/migrations": "^2.2",
+ "php": "^7.1",
+ "symfony/framework-bundle": "~3.4|~4.0|~5.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^5.0",
+ "mikey179/vfsstream": "^1.6",
+ "phpstan/phpstan": "^0.9.2",
+ "phpstan/phpstan-strict-rules": "^0.9",
+ "phpunit/phpunit": "^6.4|^7.0"
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Bundle\\MigrationsBundle\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Doctrine Project",
+ "homepage": "http://www.doctrine-project.org"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "http://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony DoctrineMigrationsBundle",
+ "homepage": "https://www.doctrine-project.org",
+ "keywords": [
+ "dbal",
+ "migrations",
+ "schema"
+ ],
+ "time": "2019-11-13T12:57:41+00:00"
+ },
+ {
+ "name": "doctrine/event-manager",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/event-manager.git",
+ "reference": "629572819973f13486371cb611386eb17851e85c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/event-manager/zipball/629572819973f13486371cb611386eb17851e85c",
+ "reference": "629572819973f13486371cb611386eb17851e85c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "conflict": {
+ "doctrine/common": "<2.9@dev"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^6.0",
+ "phpunit/phpunit": "^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\": "lib/Doctrine/Common"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ },
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com"
+ }
+ ],
+ "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.",
+ "homepage": "https://www.doctrine-project.org/projects/event-manager.html",
+ "keywords": [
+ "event",
+ "event dispatcher",
+ "event manager",
+ "event system",
+ "events"
+ ],
+ "time": "2019-11-10T09:48:07+00:00"
+ },
+ {
+ "name": "doctrine/inflector",
+ "version": "1.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/inflector.git",
+ "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/inflector/zipball/ec3a55242203ffa6a4b27c58176da97ff0a7aec1",
+ "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Common String Manipulations with regard to casing and singular/plural rules.",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "inflection",
+ "pluralize",
+ "singularize",
+ "string"
+ ],
+ "time": "2019-10-30T19:59:35+00:00"
+ },
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "ae466f726242e637cebdd526a7d991b9433bacf1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1",
+ "reference": "ae466f726242e637cebdd526a7d991b9433bacf1",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^6.0",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpbench/phpbench": "^0.13",
+ "phpstan/phpstan-phpunit": "^0.11",
+ "phpstan/phpstan-shim": "^0.11",
+ "phpunit/phpunit": "^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "http://ocramius.github.com/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "time": "2019-10-21T16:45:58+00:00"
+ },
+ {
+ "name": "doctrine/lexer",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/lexer.git",
+ "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/lexer/zipball/5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6",
+ "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^6.0",
+ "phpstan/phpstan": "^0.11.8",
+ "phpunit/phpunit": "^8.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
+ "homepage": "https://www.doctrine-project.org/projects/lexer.html",
+ "keywords": [
+ "annotations",
+ "docblock",
+ "lexer",
+ "parser",
+ "php"
+ ],
+ "time": "2019-10-30T14:39:59+00:00"
+ },
+ {
+ "name": "doctrine/migrations",
+ "version": "2.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/migrations.git",
+ "reference": "a3987131febeb0e9acb3c47ab0df0af004588934"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/migrations/zipball/a3987131febeb0e9acb3c47ab0df0af004588934",
+ "reference": "a3987131febeb0e9acb3c47ab0df0af004588934",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/dbal": "^2.9",
+ "ocramius/package-versions": "^1.3",
+ "ocramius/proxy-manager": "^2.0.2",
+ "php": "^7.1",
+ "symfony/console": "^3.4||^4.0||^5.0",
+ "symfony/stopwatch": "^3.4||^4.0||^5.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^6.0",
+ "doctrine/orm": "^2.6",
+ "ext-pdo_sqlite": "*",
+ "jdorn/sql-formatter": "^1.1",
+ "mikey179/vfsstream": "^1.6",
+ "phpstan/phpstan": "^0.10",
+ "phpstan/phpstan-deprecation-rules": "^0.10",
+ "phpstan/phpstan-phpunit": "^0.10",
+ "phpstan/phpstan-strict-rules": "^0.10",
+ "phpunit/phpunit": "^7.0",
+ "symfony/process": "^3.4||^4.0||^5.0",
+ "symfony/yaml": "^3.4||^4.0||^5.0"
+ },
+ "suggest": {
+ "jdorn/sql-formatter": "Allows to generate formatted SQL with the diff command.",
+ "symfony/yaml": "Allows the use of yaml for migration configuration files."
+ },
+ "bin": [
+ "bin/doctrine-migrations"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Migrations\\": "lib/Doctrine/Migrations"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Michael Simonson",
+ "email": "contact@mikesimonson.com"
+ }
+ ],
+ "description": "PHP Doctrine Migrations project offer additional functionality on top of the database abstraction layer (DBAL) for versioning your database schema and easily deploying changes to it. It is a very easy to use and a powerful tool.",
+ "homepage": "https://www.doctrine-project.org/projects/migrations.html",
+ "keywords": [
+ "database",
+ "dbal",
+ "migrations",
+ "php"
+ ],
+ "time": "2019-12-04T06:09:14+00:00"
+ },
+ {
+ "name": "doctrine/orm",
+ "version": "v2.7.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/orm.git",
+ "reference": "dafe298ce5d0b995ebe1746670704c0a35868a6a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/orm/zipball/dafe298ce5d0b995ebe1746670704c0a35868a6a",
+ "reference": "dafe298ce5d0b995ebe1746670704c0a35868a6a",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/annotations": "^1.8",
+ "doctrine/cache": "^1.9.1",
+ "doctrine/collections": "^1.5",
+ "doctrine/common": "^2.11",
+ "doctrine/dbal": "^2.9.3",
+ "doctrine/event-manager": "^1.1",
+ "doctrine/instantiator": "^1.3",
+ "doctrine/persistence": "^1.2",
+ "ext-pdo": "*",
+ "ocramius/package-versions": "^1.2",
+ "php": "^7.1",
+ "symfony/console": "^3.0|^4.0|^5.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^5.0",
+ "phpunit/phpunit": "^7.5",
+ "symfony/yaml": "^3.4|^4.0|^5.0"
+ },
+ "suggest": {
+ "symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
+ },
+ "bin": [
+ "bin/doctrine"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.7.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\ORM\\": "lib/Doctrine/ORM"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com"
+ }
+ ],
+ "description": "Object-Relational-Mapper for PHP",
+ "homepage": "https://www.doctrine-project.org/projects/orm.html",
+ "keywords": [
+ "database",
+ "orm"
+ ],
+ "time": "2020-03-19T06:41:02+00:00"
+ },
+ {
+ "name": "doctrine/persistence",
+ "version": "1.3.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/persistence.git",
+ "reference": "0af483f91bada1c9ded6c2cfd26ab7d5ab2094e0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/persistence/zipball/0af483f91bada1c9ded6c2cfd26ab7d5ab2094e0",
+ "reference": "0af483f91bada1c9ded6c2cfd26ab7d5ab2094e0",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/annotations": "^1.0",
+ "doctrine/cache": "^1.0",
+ "doctrine/collections": "^1.0",
+ "doctrine/event-manager": "^1.0",
+ "doctrine/reflection": "^1.2",
+ "php": "^7.1"
+ },
+ "conflict": {
+ "doctrine/common": "<2.10@dev"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^6.0",
+ "phpstan/phpstan": "^0.11",
+ "phpunit/phpunit": "^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\": "lib/Doctrine/Common",
+ "Doctrine\\Persistence\\": "lib/Doctrine/Persistence"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ },
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com"
+ }
+ ],
+ "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.",
+ "homepage": "https://doctrine-project.org/projects/persistence.html",
+ "keywords": [
+ "mapper",
+ "object",
+ "odm",
+ "orm",
+ "persistence"
+ ],
+ "time": "2020-03-21T15:13:52+00:00"
+ },
+ {
+ "name": "doctrine/reflection",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/reflection.git",
+ "reference": "b699ecc7f2784d1e49924fd9858cf1078db6b0e2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/reflection/zipball/b699ecc7f2784d1e49924fd9858cf1078db6b0e2",
+ "reference": "b699ecc7f2784d1e49924fd9858cf1078db6b0e2",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/annotations": "^1.0",
+ "ext-tokenizer": "*",
+ "php": "^7.1"
+ },
+ "conflict": {
+ "doctrine/common": "<2.9"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^5.0",
+ "doctrine/common": "^2.10",
+ "phpstan/phpstan": "^0.11.0",
+ "phpstan/phpstan-phpunit": "^0.11.0",
+ "phpunit/phpunit": "^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\": "lib/Doctrine/Common"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ },
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com"
+ }
+ ],
+ "description": "The Doctrine Reflection project is a simple library used by the various Doctrine projects which adds some additional functionality on top of the reflection functionality that comes with PHP. It allows you to get the reflection information about classes, methods and properties statically.",
+ "homepage": "https://www.doctrine-project.org/projects/reflection.html",
+ "keywords": [
+ "reflection",
+ "static"
+ ],
+ "time": "2020-03-21T11:34:59+00:00"
+ },
+ {
+ "name": "egulias/email-validator",
+ "version": "2.1.17",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/egulias/EmailValidator.git",
+ "reference": "ade6887fd9bd74177769645ab5c474824f8a418a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ade6887fd9bd74177769645ab5c474824f8a418a",
+ "reference": "ade6887fd9bd74177769645ab5c474824f8a418a",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/lexer": "^1.0.1",
+ "php": ">=5.5",
+ "symfony/polyfill-intl-idn": "^1.10"
+ },
+ "require-dev": {
+ "dominicsayers/isemail": "^3.0.7",
+ "phpunit/phpunit": "^4.8.36|^7.5.15",
+ "satooshi/php-coveralls": "^1.0.1"
+ },
+ "suggest": {
+ "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Egulias\\EmailValidator\\": "EmailValidator"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Eduardo Gulias Davis"
+ }
+ ],
+ "description": "A library for validating emails against several RFCs",
+ "homepage": "https://github.com/egulias/EmailValidator",
+ "keywords": [
+ "email",
+ "emailvalidation",
+ "emailvalidator",
+ "validation",
+ "validator"
+ ],
+ "time": "2020-02-13T22:36:52+00:00"
+ },
+ {
+ "name": "jdorn/sql-formatter",
+ "version": "v1.2.17",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jdorn/sql-formatter.git",
+ "reference": "64990d96e0959dff8e059dfcdc1af130728d92bc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jdorn/sql-formatter/zipball/64990d96e0959dff8e059dfcdc1af130728d92bc",
+ "reference": "64990d96e0959dff8e059dfcdc1af130728d92bc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.2.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "3.7.*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "lib"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jeremy Dorn",
+ "email": "jeremy@jeremydorn.com",
+ "homepage": "http://jeremydorn.com/"
+ }
+ ],
+ "description": "a PHP SQL highlighting library",
+ "homepage": "https://github.com/jdorn/sql-formatter/",
+ "keywords": [
+ "highlight",
+ "sql"
+ ],
+ "time": "2014-01-12T16:20:24+00:00"
+ },
+ {
+ "name": "monolog/monolog",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Seldaek/monolog.git",
+ "reference": "c861fcba2ca29404dc9e617eedd9eff4616986b8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c861fcba2ca29404dc9e617eedd9eff4616986b8",
+ "reference": "c861fcba2ca29404dc9e617eedd9eff4616986b8",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2",
+ "psr/log": "^1.0.1"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0.0"
+ },
+ "require-dev": {
+ "aws/aws-sdk-php": "^2.4.9 || ^3.0",
+ "doctrine/couchdb": "~1.0@dev",
+ "elasticsearch/elasticsearch": "^6.0",
+ "graylog2/gelf-php": "^1.4.2",
+ "jakub-onderka/php-parallel-lint": "^0.9",
+ "php-amqplib/php-amqplib": "~2.4",
+ "php-console/php-console": "^3.1.3",
+ "phpspec/prophecy": "^1.6.1",
+ "phpunit/phpunit": "^8.3",
+ "predis/predis": "^1.1",
+ "rollbar/rollbar": "^1.3",
+ "ruflin/elastica": ">=0.90 <3.0",
+ "swiftmailer/swiftmailer": "^5.3|^6.0"
+ },
+ "suggest": {
+ "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+ "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+ "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
+ "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+ "ext-mbstring": "Allow to work properly with unicode symbols",
+ "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
+ "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+ "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
+ "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+ "php-console/php-console": "Allow sending log messages to Google Chrome",
+ "rollbar/rollbar": "Allow sending log messages to Rollbar",
+ "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Monolog\\": "src/Monolog"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+ "homepage": "http://github.com/Seldaek/monolog",
+ "keywords": [
+ "log",
+ "logging",
+ "psr-3"
+ ],
+ "time": "2019-12-20T14:22:59+00:00"
+ },
+ {
+ "name": "ocramius/package-versions",
+ "version": "1.4.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Ocramius/PackageVersions.git",
+ "reference": "44af6f3a2e2e04f2af46bcb302ad9600cba41c7d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/44af6f3a2e2e04f2af46bcb302ad9600cba41c7d",
+ "reference": "44af6f3a2e2e04f2af46bcb302ad9600cba41c7d",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0.0",
+ "php": "^7.1.0"
+ },
+ "require-dev": {
+ "composer/composer": "^1.6.3",
+ "doctrine/coding-standard": "^5.0.1",
+ "ext-zip": "*",
+ "infection/infection": "^0.7.1",
+ "phpunit/phpunit": "^7.5.17"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "PackageVersions\\Installer",
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PackageVersions\\": "src/PackageVersions"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com"
+ }
+ ],
+ "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
+ "time": "2019-11-15T16:17:10+00:00"
+ },
+ {
+ "name": "ocramius/proxy-manager",
+ "version": "2.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Ocramius/ProxyManager.git",
+ "reference": "4d154742e31c35137d5374c998e8f86b54db2e2f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/4d154742e31c35137d5374c998e8f86b54db2e2f",
+ "reference": "4d154742e31c35137d5374c998e8f86b54db2e2f",
+ "shasum": ""
+ },
+ "require": {
+ "ocramius/package-versions": "^1.1.3",
+ "php": "^7.2.0",
+ "zendframework/zend-code": "^3.3.0"
+ },
+ "require-dev": {
+ "couscous/couscous": "^1.6.1",
+ "ext-phar": "*",
+ "humbug/humbug": "1.0.0-RC.0@RC",
+ "nikic/php-parser": "^3.1.1",
+ "padraic/phpunit-accelerator": "dev-master@DEV",
+ "phpbench/phpbench": "^0.12.2",
+ "phpstan/phpstan": "dev-master#856eb10a81c1d27c701a83f167dc870fd8f4236a as 0.9.999",
+ "phpstan/phpstan-phpunit": "dev-master#5629c0a1f4a9c417cb1077cf6693ad9753895761",
+ "phpunit/phpunit": "^6.4.3",
+ "squizlabs/php_codesniffer": "^2.9.1"
+ },
+ "suggest": {
+ "ocramius/generated-hydrator": "To have very fast object to array to object conversion for ghost objects",
+ "zendframework/zend-json": "To have the JsonRpc adapter (Remote Object feature)",
+ "zendframework/zend-soap": "To have the Soap adapter (Remote Object feature)",
+ "zendframework/zend-xmlrpc": "To have the XmlRpc adapter (Remote Object feature)"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "ProxyManager\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "http://ocramius.github.io/"
+ }
+ ],
+ "description": "A library providing utilities to generate, instantiate and generally operate with Object Proxies",
+ "homepage": "https://github.com/Ocramius/ProxyManager",
+ "keywords": [
+ "aop",
+ "lazy loading",
+ "proxy",
+ "proxy pattern",
+ "service proxies"
+ ],
+ "time": "2019-08-10T08:37:15+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-common",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a",
+ "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "time": "2018-08-07T13:53:10+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "4.3.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c",
+ "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0",
+ "phpdocumentor/type-resolver": "~0.4 || ^1.0.0",
+ "webmozart/assert": "^1.0"
+ },
+ "require-dev": {
+ "doctrine/instantiator": "^1.0.5",
+ "mockery/mockery": "^1.0",
+ "phpdocumentor/type-resolver": "0.4.*",
+ "phpunit/phpunit": "^6.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+ "time": "2019-12-28T18:55:12+00:00"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "7462d5f123dfc080dfdf26897032a6513644fc95"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95",
+ "reference": "7462d5f123dfc080dfdf26897032a6513644fc95",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2",
+ "phpdocumentor/reflection-common": "^2.0"
+ },
+ "require-dev": {
+ "ext-tokenizer": "^7.2",
+ "mockery/mockery": "~1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
+ "time": "2020-02-18T18:59:58+00:00"
+ },
+ {
+ "name": "psr/cache",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/cache.git",
+ "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
+ "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Cache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for caching libraries",
+ "keywords": [
+ "cache",
+ "psr",
+ "psr-6"
+ ],
+ "time": "2016-08-06T20:24:11+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+ "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "time": "2017-02-14T16:28:37+00:00"
+ },
+ {
+ "name": "psr/event-dispatcher",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/event-dispatcher.git",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\EventDispatcher\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Standard interfaces for event handling.",
+ "keywords": [
+ "events",
+ "psr",
+ "psr-14"
+ ],
+ "time": "2019-01-08T18:20:26+00:00"
+ },
+ {
+ "name": "psr/link",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/link.git",
+ "reference": "eea8e8662d5cd3ae4517c9b864493f59fca95562"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/link/zipball/eea8e8662d5cd3ae4517c9b864493f59fca95562",
+ "reference": "eea8e8662d5cd3ae4517c9b864493f59fca95562",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Link\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interfaces for HTTP links",
+ "keywords": [
+ "http",
+ "http-link",
+ "link",
+ "psr",
+ "psr-13",
+ "rest"
+ ],
+ "time": "2016-10-28T16:06:13+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "1.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
+ "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "time": "2020-03-23T09:12:05+00:00"
+ },
+ {
+ "name": "sensio/framework-extra-bundle",
+ "version": "v5.5.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git",
+ "reference": "98f0807137b13d0acfdf3c255a731516e97015de"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/98f0807137b13d0acfdf3c255a731516e97015de",
+ "reference": "98f0807137b13d0acfdf3c255a731516e97015de",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/annotations": "^1.0",
+ "php": ">=7.1.3",
+ "symfony/config": "^4.3|^5.0",
+ "symfony/dependency-injection": "^4.3|^5.0",
+ "symfony/framework-bundle": "^4.3|^5.0",
+ "symfony/http-kernel": "^4.3|^5.0"
+ },
+ "conflict": {
+ "doctrine/doctrine-cache-bundle": "<1.3.1"
+ },
+ "require-dev": {
+ "doctrine/doctrine-bundle": "^1.11|^2.0",
+ "doctrine/orm": "^2.5",
+ "nyholm/psr7": "^1.1",
+ "symfony/browser-kit": "^4.3|^5.0",
+ "symfony/dom-crawler": "^4.3|^5.0",
+ "symfony/expression-language": "^4.3|^5.0",
+ "symfony/finder": "^4.3|^5.0",
+ "symfony/monolog-bridge": "^4.0|^5.0",
+ "symfony/monolog-bundle": "^3.2",
+ "symfony/phpunit-bridge": "^4.3.5|^5.0",
+ "symfony/psr-http-message-bridge": "^1.1",
+ "symfony/security-bundle": "^4.3|^5.0",
+ "symfony/twig-bundle": "^4.3|^5.0",
+ "symfony/yaml": "^4.3|^5.0",
+ "twig/twig": "^1.34|^2.4|^3.0"
+ },
+ "suggest": {
+ "symfony/expression-language": "",
+ "symfony/psr-http-message-bridge": "To use the PSR-7 converters",
+ "symfony/security-bundle": ""
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Sensio\\Bundle\\FrameworkExtraBundle\\": "src/"
+ },
+ "exclude-from-classmap": [
+ "/tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "description": "This bundle provides a way to configure your controllers with annotations",
+ "keywords": [
+ "annotations",
+ "controllers"
+ ],
+ "time": "2019-12-27T08:57:19+00:00"
+ },
+ {
+ "name": "symfony/asset",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/asset.git",
+ "reference": "b9d7f8609849c71e79a0702fdaa453c1329b0c2c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/asset/zipball/b9d7f8609849c71e79a0702fdaa453c1329b0c2c",
+ "reference": "b9d7f8609849c71e79a0702fdaa453c1329b0c2c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5"
+ },
+ "require-dev": {
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/http-kernel": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/http-foundation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Asset\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Asset Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-24T15:05:31+00:00"
+ },
+ {
+ "name": "symfony/cache",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/cache.git",
+ "reference": "c6255e419e8592dab7de6e29b014ae9e8926989a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/cache/zipball/c6255e419e8592dab7de6e29b014ae9e8926989a",
+ "reference": "c6255e419e8592dab7de6e29b014ae9e8926989a",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "psr/cache": "~1.0",
+ "psr/log": "~1.0",
+ "symfony/cache-contracts": "^1.1.7|^2",
+ "symfony/service-contracts": "^1.1|^2",
+ "symfony/var-exporter": "^4.4|^5.0"
+ },
+ "conflict": {
+ "doctrine/dbal": "<2.5",
+ "symfony/dependency-injection": "<4.4",
+ "symfony/http-kernel": "<4.4",
+ "symfony/var-dumper": "<4.4"
+ },
+ "provide": {
+ "psr/cache-implementation": "1.0",
+ "psr/simple-cache-implementation": "1.0",
+ "symfony/cache-implementation": "1.0"
+ },
+ "require-dev": {
+ "cache/integration-tests": "dev-master",
+ "doctrine/cache": "~1.6",
+ "doctrine/dbal": "~2.5",
+ "predis/predis": "~1.1",
+ "psr/simple-cache": "^1.0",
+ "symfony/config": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/var-dumper": "^4.4|^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Cache\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Cache component with PSR-6, PSR-16, and tags",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "caching",
+ "psr6"
+ ],
+ "time": "2020-02-24T15:05:31+00:00"
+ },
+ {
+ "name": "symfony/cache-contracts",
+ "version": "v2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/cache-contracts.git",
+ "reference": "23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16",
+ "reference": "23ed8bfc1a4115feca942cb5f1aacdf3dcdf3c16",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "psr/cache": "^1.0"
+ },
+ "suggest": {
+ "symfony/cache-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Cache\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to caching",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "time": "2019-11-18T17:27:11+00:00"
+ },
+ {
+ "name": "symfony/config",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/config.git",
+ "reference": "938905f46df484b2aeae9016fd658aed577cdceb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/config/zipball/938905f46df484b2aeae9016fd658aed577cdceb",
+ "reference": "938905f46df484b2aeae9016fd658aed577cdceb",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/filesystem": "^4.4|^5.0",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "conflict": {
+ "symfony/finder": "<4.4"
+ },
+ "require-dev": {
+ "symfony/event-dispatcher": "^4.4|^5.0",
+ "symfony/finder": "^4.4|^5.0",
+ "symfony/messenger": "^4.4|^5.0",
+ "symfony/service-contracts": "^1.1|^2",
+ "symfony/yaml": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/yaml": "To use the yaml reference dumper"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Config\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Config Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-04T09:41:09+00:00"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "d29e2d36941de13600c399e393a60b8cfe59ac49"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/d29e2d36941de13600c399e393a60b8cfe59ac49",
+ "reference": "d29e2d36941de13600c399e393a60b8cfe59ac49",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/polyfill-php73": "^1.8",
+ "symfony/service-contracts": "^1.1|^2"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<4.4",
+ "symfony/event-dispatcher": "<4.4",
+ "symfony/lock": "<4.4",
+ "symfony/process": "<4.4"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/event-dispatcher": "^4.4|^5.0",
+ "symfony/lock": "^4.4|^5.0",
+ "symfony/process": "^4.4|^5.0",
+ "symfony/var-dumper": "^4.4|^5.0"
+ },
+ "suggest": {
+ "psr/log": "For using the console logger",
+ "symfony/event-dispatcher": "",
+ "symfony/lock": "",
+ "symfony/process": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Console Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-24T15:05:31+00:00"
+ },
+ {
+ "name": "symfony/dependency-injection",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/dependency-injection.git",
+ "reference": "3575004a9b0d51ead83473ec90121045b3a0b56f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/3575004a9b0d51ead83473ec90121045b3a0b56f",
+ "reference": "3575004a9b0d51ead83473ec90121045b3a0b56f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "psr/container": "^1.0",
+ "symfony/service-contracts": "^1.1.6|^2"
+ },
+ "conflict": {
+ "symfony/config": "<5.0",
+ "symfony/finder": "<4.4",
+ "symfony/proxy-manager-bridge": "<4.4",
+ "symfony/yaml": "<4.4"
+ },
+ "provide": {
+ "psr/container-implementation": "1.0",
+ "symfony/service-implementation": "1.0"
+ },
+ "require-dev": {
+ "symfony/config": "^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/yaml": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/config": "",
+ "symfony/expression-language": "For using expressions in service container configuration",
+ "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required",
+ "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them",
+ "symfony/yaml": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\DependencyInjection\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony DependencyInjection Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-24T15:05:31+00:00"
+ },
+ {
+ "name": "symfony/doctrine-bridge",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/doctrine-bridge.git",
+ "reference": "671f9afc0294e1a2fa5661fc5b8e53dd0ec85b7b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/671f9afc0294e1a2fa5661fc5b8e53dd0ec85b7b",
+ "reference": "671f9afc0294e1a2fa5661fc5b8e53dd0ec85b7b",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/event-manager": "~1.0",
+ "doctrine/persistence": "^1.3",
+ "php": "^7.2.5",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/service-contracts": "^1.1|^2"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<5.4.3",
+ "symfony/dependency-injection": "<4.4",
+ "symfony/form": "<5",
+ "symfony/http-kernel": "<5",
+ "symfony/messenger": "<4.4",
+ "symfony/property-info": "<5",
+ "symfony/security-bundle": "<5",
+ "symfony/security-core": "<5",
+ "symfony/validator": "<5.0.2"
+ },
+ "require-dev": {
+ "doctrine/annotations": "~1.7",
+ "doctrine/cache": "~1.6",
+ "doctrine/collections": "~1.0",
+ "doctrine/data-fixtures": "1.0.*",
+ "doctrine/dbal": "~2.4",
+ "doctrine/orm": "^2.6.3",
+ "doctrine/reflection": "~1.0",
+ "symfony/config": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/form": "^5.0",
+ "symfony/http-kernel": "^5.0",
+ "symfony/messenger": "^4.4|^5.0",
+ "symfony/property-access": "^4.4|^5.0",
+ "symfony/property-info": "^5.0",
+ "symfony/proxy-manager-bridge": "^4.4|^5.0",
+ "symfony/security-core": "^5.0",
+ "symfony/stopwatch": "^4.4|^5.0",
+ "symfony/translation": "^4.4|^5.0",
+ "symfony/validator": "^5.0.2",
+ "symfony/var-dumper": "^4.4|^5.0"
+ },
+ "suggest": {
+ "doctrine/data-fixtures": "",
+ "doctrine/dbal": "",
+ "doctrine/orm": "",
+ "symfony/form": "",
+ "symfony/property-info": "",
+ "symfony/validator": ""
+ },
+ "type": "symfony-bridge",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bridge\\Doctrine\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Doctrine Bridge",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-25T14:24:11+00:00"
+ },
+ {
+ "name": "symfony/dotenv",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/dotenv.git",
+ "reference": "48c8fdda51e5b24d031ce8ec221caa186337e36f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/dotenv/zipball/48c8fdda51e5b24d031ce8ec221caa186337e36f",
+ "reference": "48c8fdda51e5b24d031ce8ec221caa186337e36f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5"
+ },
+ "require-dev": {
+ "symfony/process": "^4.4|^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Dotenv\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Registers environment variables from a .env file",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "dotenv",
+ "env",
+ "environment"
+ ],
+ "time": "2020-02-29T10:07:09+00:00"
+ },
+ {
+ "name": "symfony/error-handler",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/error-handler.git",
+ "reference": "24a938d9913f42d006ee1ca0164ea1f29c1067ec"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/error-handler/zipball/24a938d9913f42d006ee1ca0164ea1f29c1067ec",
+ "reference": "24a938d9913f42d006ee1ca0164ea1f29c1067ec",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "psr/log": "^1.0",
+ "symfony/var-dumper": "^4.4|^5.0"
+ },
+ "require-dev": {
+ "symfony/http-kernel": "^4.4|^5.0",
+ "symfony/serializer": "^4.4|^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\ErrorHandler\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony ErrorHandler Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-29T10:07:09+00:00"
+ },
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher.git",
+ "reference": "b45ad88b253c5a9702ce218e201d89c85d148cea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b45ad88b253c5a9702ce218e201d89c85d148cea",
+ "reference": "b45ad88b253c5a9702ce218e201d89c85d148cea",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/event-dispatcher-contracts": "^2"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<4.4"
+ },
+ "provide": {
+ "psr/event-dispatcher-implementation": "1.0",
+ "symfony/event-dispatcher-implementation": "2.0"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/service-contracts": "^1.1|^2",
+ "symfony/stopwatch": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\EventDispatcher\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony EventDispatcher Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-22T20:09:08+00:00"
+ },
+ {
+ "name": "symfony/event-dispatcher-contracts",
+ "version": "v2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher-contracts.git",
+ "reference": "af23c2584d4577d54661c434446fb8fbed6025dd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/af23c2584d4577d54661c434446fb8fbed6025dd",
+ "reference": "af23c2584d4577d54661c434446fb8fbed6025dd",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "psr/event-dispatcher": "^1"
+ },
+ "suggest": {
+ "symfony/event-dispatcher-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\EventDispatcher\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to dispatching event",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "time": "2019-11-18T17:27:11+00:00"
+ },
+ {
+ "name": "symfony/expression-language",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/expression-language.git",
+ "reference": "67741ad12ac7fcc157c51d335e66c7b6a475f9b2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/expression-language/zipball/67741ad12ac7fcc157c51d335e66c7b6a475f9b2",
+ "reference": "67741ad12ac7fcc157c51d335e66c7b6a475f9b2",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/cache": "^4.4|^5.0",
+ "symfony/service-contracts": "^1.1|^2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\ExpressionLanguage\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony ExpressionLanguage Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-24T15:05:31+00:00"
+ },
+ {
+ "name": "symfony/filesystem",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "3afadc0f57cd74f86379d073e694b0f2cda2a88c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/3afadc0f57cd74f86379d073e694b0f2cda2a88c",
+ "reference": "3afadc0f57cd74f86379d073e694b0f2cda2a88c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Filesystem Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-01-21T08:40:24+00:00"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "6251f201187ca9d66f6b099d3de65d279e971138"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/6251f201187ca9d66f6b099d3de65d279e971138",
+ "reference": "6251f201187ca9d66f6b099d3de65d279e971138",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Finder Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-14T07:43:07+00:00"
+ },
+ {
+ "name": "symfony/flex",
+ "version": "v1.6.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/flex.git",
+ "reference": "e4f5a2653ca503782a31486198bd1dd1c9a47f83"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/flex/zipball/e4f5a2653ca503782a31486198bd1dd1c9a47f83",
+ "reference": "e4f5a2653ca503782a31486198bd1dd1c9a47f83",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0",
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "composer/composer": "^1.0.2",
+ "symfony/dotenv": "^3.4|^4.0|^5.0",
+ "symfony/phpunit-bridge": "^3.4.19|^4.1.8|^5.0",
+ "symfony/process": "^2.7|^3.0|^4.0|^5.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.5-dev"
+ },
+ "class": "Symfony\\Flex\\Flex"
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Flex\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien.potencier@gmail.com"
+ }
+ ],
+ "description": "Composer plugin for Symfony",
+ "time": "2020-01-30T12:06:45+00:00"
+ },
+ {
+ "name": "symfony/form",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/form.git",
+ "reference": "7d3afc4f0776904bb199317ae71b6a0fc46e5e5d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/form/zipball/7d3afc4f0776904bb199317ae71b6a0fc46e5e5d",
+ "reference": "7d3afc4f0776904bb199317ae71b6a0fc46e5e5d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/event-dispatcher": "^4.4|^5.0",
+ "symfony/intl": "^4.4|^5.0",
+ "symfony/options-resolver": "^5.0",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/property-access": "^5.0",
+ "symfony/service-contracts": "^1.1|^2"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<5.4.3",
+ "symfony/console": "<4.4",
+ "symfony/dependency-injection": "<4.4",
+ "symfony/doctrine-bridge": "<4.4",
+ "symfony/framework-bundle": "<4.4",
+ "symfony/http-kernel": "<4.4",
+ "symfony/intl": "<4.4",
+ "symfony/translation": "<4.4",
+ "symfony/twig-bridge": "<4.4"
+ },
+ "require-dev": {
+ "doctrine/collections": "~1.0",
+ "symfony/config": "^4.4|^5.0",
+ "symfony/console": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/http-kernel": "^4.4|^5.0",
+ "symfony/security-csrf": "^4.4|^5.0",
+ "symfony/translation": "^4.4|^5.0",
+ "symfony/validator": "^4.4|^5.0",
+ "symfony/var-dumper": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/security-csrf": "For protecting forms against CSRF attacks.",
+ "symfony/twig-bridge": "For templating with Twig.",
+ "symfony/validator": "For form validation."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Form\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Form Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-29T10:07:09+00:00"
+ },
+ {
+ "name": "symfony/framework-bundle",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/framework-bundle.git",
+ "reference": "fc6a0059fedaaf15efc66b64b7a3cedaa4b1edf4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/fc6a0059fedaaf15efc66b64b7a3cedaa4b1edf4",
+ "reference": "fc6a0059fedaaf15efc66b64b7a3cedaa4b1edf4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-xml": "*",
+ "php": "^7.2.5",
+ "symfony/cache": "^4.4|^5.0",
+ "symfony/config": "^5.0",
+ "symfony/dependency-injection": "^5.0.1",
+ "symfony/error-handler": "^4.4.1|^5.0.1",
+ "symfony/filesystem": "^4.4|^5.0",
+ "symfony/finder": "^4.4|^5.0",
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/http-kernel": "^5.0",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/routing": "^5.0"
+ },
+ "conflict": {
+ "doctrine/persistence": "<1.3",
+ "phpdocumentor/reflection-docblock": "<3.0",
+ "phpdocumentor/type-resolver": "<0.2.1",
+ "phpunit/phpunit": "<5.4.3",
+ "symfony/asset": "<4.4",
+ "symfony/browser-kit": "<4.4",
+ "symfony/console": "<4.4",
+ "symfony/dom-crawler": "<4.4",
+ "symfony/dotenv": "<4.4",
+ "symfony/form": "<4.4",
+ "symfony/http-client": "<4.4",
+ "symfony/lock": "<4.4",
+ "symfony/mailer": "<4.4",
+ "symfony/messenger": "<4.4",
+ "symfony/mime": "<4.4",
+ "symfony/property-info": "<4.4",
+ "symfony/serializer": "<4.4",
+ "symfony/stopwatch": "<4.4",
+ "symfony/translation": "<5.0",
+ "symfony/twig-bridge": "<4.4",
+ "symfony/twig-bundle": "<4.4",
+ "symfony/validator": "<4.4",
+ "symfony/web-profiler-bundle": "<4.4",
+ "symfony/workflow": "<4.4"
+ },
+ "require-dev": {
+ "doctrine/annotations": "~1.7",
+ "doctrine/cache": "~1.0",
+ "paragonie/sodium_compat": "^1.8",
+ "phpdocumentor/reflection-docblock": "^3.0|^4.0",
+ "symfony/asset": "^4.4|^5.0",
+ "symfony/browser-kit": "^4.4|^5.0",
+ "symfony/console": "^4.4|^5.0",
+ "symfony/css-selector": "^4.4|^5.0",
+ "symfony/dom-crawler": "^4.4|^5.0",
+ "symfony/dotenv": "^4.4|^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/form": "^4.4|^5.0",
+ "symfony/http-client": "^4.4|^5.0",
+ "symfony/lock": "^4.4|^5.0",
+ "symfony/mailer": "^4.4|^5.0",
+ "symfony/messenger": "^4.4|^5.0",
+ "symfony/mime": "^4.4|^5.0",
+ "symfony/polyfill-intl-icu": "~1.0",
+ "symfony/process": "^4.4|^5.0",
+ "symfony/property-info": "^4.4|^5.0",
+ "symfony/security-csrf": "^4.4|^5.0",
+ "symfony/security-http": "^4.4|^5.0",
+ "symfony/serializer": "^4.4|^5.0",
+ "symfony/stopwatch": "^4.4|^5.0",
+ "symfony/string": "~5.0.0",
+ "symfony/translation": "^5.0",
+ "symfony/twig-bundle": "^4.4|^5.0",
+ "symfony/validator": "^4.4|^5.0",
+ "symfony/web-link": "^4.4|^5.0",
+ "symfony/workflow": "^4.4|^5.0",
+ "symfony/yaml": "^4.4|^5.0",
+ "twig/twig": "^2.10|^3.0"
+ },
+ "suggest": {
+ "ext-apcu": "For best performance of the system caches",
+ "symfony/console": "For using the console commands",
+ "symfony/form": "For using forms",
+ "symfony/property-info": "For using the property_info service",
+ "symfony/serializer": "For using the serializer service",
+ "symfony/validator": "For using validation",
+ "symfony/web-link": "For using web links, features such as preloading, prefetching or prerendering",
+ "symfony/yaml": "For using the debug:config and lint:yaml commands"
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bundle\\FrameworkBundle\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony FrameworkBundle",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-29T10:07:09+00:00"
+ },
+ {
+ "name": "symfony/http-client",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-client.git",
+ "reference": "2edd40250649944775aad5d6b4cc8e164c1e9d72"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/2edd40250649944775aad5d6b4cc8e164c1e9d72",
+ "reference": "2edd40250649944775aad5d6b4cc8e164c1e9d72",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "psr/log": "^1.0",
+ "symfony/http-client-contracts": "^1.1.8|^2",
+ "symfony/polyfill-php73": "^1.11",
+ "symfony/service-contracts": "^1.0|^2"
+ },
+ "provide": {
+ "php-http/async-client-implementation": "*",
+ "php-http/client-implementation": "*",
+ "psr/http-client-implementation": "1.0",
+ "symfony/http-client-implementation": "1.1"
+ },
+ "require-dev": {
+ "guzzlehttp/promises": "^1.3.1",
+ "nyholm/psr7": "^1.0",
+ "php-http/httplug": "^1.0|^2.0",
+ "psr/http-client": "^1.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/http-kernel": "^4.4|^5.0",
+ "symfony/process": "^4.4|^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpClient\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony HttpClient component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-26T22:30:10+00:00"
+ },
+ {
+ "name": "symfony/http-client-contracts",
+ "version": "v2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-client-contracts.git",
+ "reference": "378868b61b85c5cac6822d4f84e26999c9f2e881"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/378868b61b85c5cac6822d4f84e26999c9f2e881",
+ "reference": "378868b61b85c5cac6822d4f84e26999c9f2e881",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5"
+ },
+ "suggest": {
+ "symfony/http-client-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\HttpClient\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to HTTP clients",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "time": "2019-11-26T23:25:11+00:00"
+ },
+ {
+ "name": "symfony/http-foundation",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-foundation.git",
+ "reference": "6f9c2ba72f4295d7ce6cf9f79dbb18036291d335"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6f9c2ba72f4295d7ce6cf9f79dbb18036291d335",
+ "reference": "6f9c2ba72f4295d7ce6cf9f79dbb18036291d335",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/mime": "^4.4|^5.0",
+ "symfony/polyfill-mbstring": "~1.1"
+ },
+ "require-dev": {
+ "predis/predis": "~1.0",
+ "symfony/expression-language": "^4.4|^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpFoundation\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony HttpFoundation Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-14T07:43:07+00:00"
+ },
+ {
+ "name": "symfony/http-kernel",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-kernel.git",
+ "reference": "021d7d54e080405678f2d8c54cb31d0bb03b4520"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/021d7d54e080405678f2d8c54cb31d0bb03b4520",
+ "reference": "021d7d54e080405678f2d8c54cb31d0bb03b4520",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "psr/log": "~1.0",
+ "symfony/error-handler": "^4.4|^5.0",
+ "symfony/event-dispatcher": "^5.0",
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/polyfill-ctype": "^1.8",
+ "symfony/polyfill-php73": "^1.9"
+ },
+ "conflict": {
+ "symfony/browser-kit": "<4.4",
+ "symfony/cache": "<5.0",
+ "symfony/config": "<5.0",
+ "symfony/dependency-injection": "<4.4",
+ "symfony/doctrine-bridge": "<5.0",
+ "symfony/form": "<5.0",
+ "symfony/http-client": "<5.0",
+ "symfony/mailer": "<5.0",
+ "symfony/messenger": "<5.0",
+ "symfony/translation": "<5.0",
+ "symfony/twig-bridge": "<5.0",
+ "symfony/validator": "<5.0",
+ "twig/twig": "<2.4"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0"
+ },
+ "require-dev": {
+ "psr/cache": "~1.0",
+ "symfony/browser-kit": "^4.4|^5.0",
+ "symfony/config": "^5.0",
+ "symfony/console": "^4.4|^5.0",
+ "symfony/css-selector": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/dom-crawler": "^4.4|^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/finder": "^4.4|^5.0",
+ "symfony/process": "^4.4|^5.0",
+ "symfony/routing": "^4.4|^5.0",
+ "symfony/stopwatch": "^4.4|^5.0",
+ "symfony/translation": "^4.4|^5.0",
+ "symfony/translation-contracts": "^1.1|^2",
+ "twig/twig": "^2.4|^3.0"
+ },
+ "suggest": {
+ "symfony/browser-kit": "",
+ "symfony/config": "",
+ "symfony/console": "",
+ "symfony/dependency-injection": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpKernel\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony HttpKernel Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-29T10:41:30+00:00"
+ },
+ {
+ "name": "symfony/inflector",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/inflector.git",
+ "reference": "e375603b6bd12e8e3aec3fc1b640ac18a4ef4cb2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/inflector/zipball/e375603b6bd12e8e3aec3fc1b640ac18a4ef4cb2",
+ "reference": "e375603b6bd12e8e3aec3fc1b640ac18a4ef4cb2",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Inflector\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Inflector Component",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "inflection",
+ "pluralize",
+ "singularize",
+ "string",
+ "symfony",
+ "words"
+ ],
+ "time": "2020-01-04T14:08:26+00:00"
+ },
+ {
+ "name": "symfony/intl",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/intl.git",
+ "reference": "2d1fb70e6e1c455a123218bebf6287d025c5bac5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/intl/zipball/2d1fb70e6e1c455a123218bebf6287d025c5bac5",
+ "reference": "2d1fb70e6e1c455a123218bebf6287d025c5bac5",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/polyfill-intl-icu": "~1.0"
+ },
+ "require-dev": {
+ "symfony/filesystem": "^4.4|^5.0"
+ },
+ "suggest": {
+ "ext-intl": "to use the component with locales other than \"en\""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Intl\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ],
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ },
+ {
+ "name": "Eriksen Costa",
+ "email": "eriksen.costa@infranology.com.br"
+ },
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A PHP replacement layer for the C intl extension that includes additional data from the ICU library.",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "i18n",
+ "icu",
+ "internationalization",
+ "intl",
+ "l10n",
+ "localization"
+ ],
+ "time": "2020-02-04T09:41:09+00:00"
+ },
+ {
+ "name": "symfony/mailer",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/mailer.git",
+ "reference": "fd0da3996c6fe31b76a354ac749a864522308243"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/mailer/zipball/fd0da3996c6fe31b76a354ac749a864522308243",
+ "reference": "fd0da3996c6fe31b76a354ac749a864522308243",
+ "shasum": ""
+ },
+ "require": {
+ "egulias/email-validator": "^2.1.10",
+ "php": "^7.2.5",
+ "psr/log": "~1.0",
+ "symfony/event-dispatcher": "^4.4|^5.0",
+ "symfony/mime": "^4.4|^5.0",
+ "symfony/service-contracts": "^1.1|^2"
+ },
+ "conflict": {
+ "symfony/http-kernel": "<4.4"
+ },
+ "require-dev": {
+ "symfony/amazon-mailer": "^4.4|^5.0",
+ "symfony/google-mailer": "^4.4|^5.0",
+ "symfony/http-client-contracts": "^1.1|^2",
+ "symfony/mailchimp-mailer": "^4.4|^5.0",
+ "symfony/mailgun-mailer": "^4.4|^5.0",
+ "symfony/messenger": "^4.4|^5.0",
+ "symfony/postmark-mailer": "^4.4|^5.0",
+ "symfony/sendgrid-mailer": "^4.4|^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Mailer\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Mailer Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-08T17:00:58+00:00"
+ },
+ {
+ "name": "symfony/mime",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/mime.git",
+ "reference": "9b3e5b5e58c56bbd76628c952d2b78556d305f3c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/mime/zipball/9b3e5b5e58c56bbd76628c952d2b78556d305f3c",
+ "reference": "9b3e5b5e58c56bbd76628c952d2b78556d305f3c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/polyfill-intl-idn": "^1.10",
+ "symfony/polyfill-mbstring": "^1.0"
+ },
+ "conflict": {
+ "symfony/mailer": "<4.4"
+ },
+ "require-dev": {
+ "egulias/email-validator": "^2.1.10",
+ "symfony/dependency-injection": "^4.4|^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Mime\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A library to manipulate MIME messages",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "mime",
+ "mime-type"
+ ],
+ "time": "2020-02-04T09:41:09+00:00"
+ },
+ {
+ "name": "symfony/monolog-bridge",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/monolog-bridge.git",
+ "reference": "3e081905b32e24742c16f7bb2cf0cd182598a32d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/3e081905b32e24742c16f7bb2cf0cd182598a32d",
+ "reference": "3e081905b32e24742c16f7bb2cf0cd182598a32d",
+ "shasum": ""
+ },
+ "require": {
+ "monolog/monolog": "^1.25.1|^2",
+ "php": "^7.2.5",
+ "symfony/http-kernel": "^4.4|^5.0",
+ "symfony/service-contracts": "^1.1|^2"
+ },
+ "conflict": {
+ "symfony/console": "<4.4",
+ "symfony/http-foundation": "<4.4"
+ },
+ "require-dev": {
+ "symfony/console": "^4.4|^5.0",
+ "symfony/http-client": "^4.4|^5.0",
+ "symfony/security-core": "^4.4|^5.0",
+ "symfony/var-dumper": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/console": "For the possibility to show log messages in console commands depending on verbosity settings.",
+ "symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel.",
+ "symfony/var-dumper": "For using the debugging handlers like the console handler or the log server handler."
+ },
+ "type": "symfony-bridge",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bridge\\Monolog\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Monolog Bridge",
+ "homepage": "https://symfony.com",
+ "time": "2020-01-04T14:08:26+00:00"
+ },
+ {
+ "name": "symfony/monolog-bundle",
+ "version": "v3.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/monolog-bundle.git",
+ "reference": "dd80460fcfe1fa2050a7103ad818e9d0686ce6fd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/dd80460fcfe1fa2050a7103ad818e9d0686ce6fd",
+ "reference": "dd80460fcfe1fa2050a7103ad818e9d0686ce6fd",
+ "shasum": ""
+ },
+ "require": {
+ "monolog/monolog": "~1.22 || ~2.0",
+ "php": ">=5.6",
+ "symfony/config": "~3.4 || ~4.0 || ^5.0",
+ "symfony/dependency-injection": "~3.4.10 || ^4.0.10 || ^5.0",
+ "symfony/http-kernel": "~3.4 || ~4.0 || ^5.0",
+ "symfony/monolog-bridge": "~3.4 || ~4.0 || ^5.0"
+ },
+ "require-dev": {
+ "symfony/console": "~3.4 || ~4.0 || ^5.0",
+ "symfony/phpunit-bridge": "^3.4.19 || ^4.0 || ^5.0",
+ "symfony/yaml": "~3.4 || ~4.0 || ^5.0"
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bundle\\MonologBundle\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "http://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony MonologBundle",
+ "homepage": "http://symfony.com",
+ "keywords": [
+ "log",
+ "logging"
+ ],
+ "time": "2019-11-13T13:11:14+00:00"
+ },
+ {
+ "name": "symfony/notifier",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/notifier.git",
+ "reference": "d41f42480963221ac0bceb38297e7460de12b168"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/notifier/zipball/d41f42480963221ac0bceb38297e7460de12b168",
+ "reference": "d41f42480963221ac0bceb38297e7460de12b168",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5"
+ },
+ "conflict": {
+ "symfony/http-kernel": "<4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Notifier\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A library to notify messages",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "notification",
+ "notifier"
+ ],
+ "time": "2020-02-24T17:03:13+00:00"
+ },
+ {
+ "name": "symfony/options-resolver",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/options-resolver.git",
+ "reference": "b1ab86ce52b0c0abe031367a173005a025e30e04"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b1ab86ce52b0c0abe031367a173005a025e30e04",
+ "reference": "b1ab86ce52b0c0abe031367a173005a025e30e04",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\OptionsResolver\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony OptionsResolver Component",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "config",
+ "configuration",
+ "options"
+ ],
+ "time": "2020-01-04T14:08:26+00:00"
+ },
+ {
+ "name": "symfony/orm-pack",
+ "version": "v1.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/orm-pack.git",
+ "reference": "c9bcc08102061f406dc908192c0f33524a675666"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/orm-pack/zipball/c9bcc08102061f406dc908192c0f33524a675666",
+ "reference": "c9bcc08102061f406dc908192c0f33524a675666",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/doctrine-bundle": "*",
+ "doctrine/doctrine-migrations-bundle": "*",
+ "doctrine/orm": "*"
+ },
+ "type": "symfony-pack",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A pack for the Doctrine ORM",
+ "time": "2020-02-10T18:03:48+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-grapheme",
+ "version": "v1.15.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+ "reference": "b6786f69dd7b062390582f20520ab4918283217e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b6786f69dd7b062390582f20520ab4918283217e",
+ "reference": "b6786f69dd7b062390582f20520ab4918283217e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.15-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's grapheme_* functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "grapheme",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2020-03-09T19:04:49+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-icu",
+ "version": "v1.15.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-icu.git",
+ "reference": "9c281272735eb66476e0fa7381e03fb0d4b60197"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/9c281272735eb66476e0fa7381e03fb0d4b60197",
+ "reference": "9c281272735eb66476e0fa7381e03fb0d4b60197",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "symfony/intl": "~2.3|~3.0|~4.0|~5.0"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.15-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's ICU-related data and classes",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "icu",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2020-02-27T09:26:54+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-idn",
+ "version": "v1.15.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-idn.git",
+ "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf",
+ "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "symfony/polyfill-mbstring": "^1.3",
+ "symfony/polyfill-php72": "^1.10"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.15-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Idn\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Laurent Bassin",
+ "email": "laurent@bassin.info"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "idn",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2020-03-09T19:04:49+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-normalizer",
+ "version": "v1.15.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+ "reference": "e62715f03f90dd8d2f3eb5daa21b4d19d71aebde"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/e62715f03f90dd8d2f3eb5daa21b4d19d71aebde",
+ "reference": "e62715f03f90dd8d2f3eb5daa21b4d19d71aebde",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.15-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ],
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's Normalizer class and related functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "intl",
+ "normalizer",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2020-02-27T09:26:54+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.15.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
+ "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.15-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2020-03-09T19:04:49+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php73",
+ "version": "v1.15.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php73.git",
+ "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7",
+ "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.15-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Php73\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ],
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2020-02-27T09:26:54+00:00"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "fd4a86dd7e36437f2fc080d8c42c7415d828a0a8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/fd4a86dd7e36437f2fc080d8c42c7415d828a0a8",
+ "reference": "fd4a86dd7e36437f2fc080d8c42c7415d828a0a8",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Process Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-08T17:00:58+00:00"
+ },
+ {
+ "name": "symfony/property-access",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/property-access.git",
+ "reference": "18617a8c26b97a262f816c78765eb3cd91630e19"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/property-access/zipball/18617a8c26b97a262f816c78765eb3cd91630e19",
+ "reference": "18617a8c26b97a262f816c78765eb3cd91630e19",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/inflector": "^4.4|^5.0"
+ },
+ "require-dev": {
+ "symfony/cache": "^4.4|^5.0"
+ },
+ "suggest": {
+ "psr/cache-implementation": "To cache access methods."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\PropertyAccess\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony PropertyAccess Component",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "access",
+ "array",
+ "extraction",
+ "index",
+ "injection",
+ "object",
+ "property",
+ "property path",
+ "reflection"
+ ],
+ "time": "2020-01-04T14:08:26+00:00"
+ },
+ {
+ "name": "symfony/property-info",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/property-info.git",
+ "reference": "8c2e9d22806acd5522691074e215bb0b04926877"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/property-info/zipball/8c2e9d22806acd5522691074e215bb0b04926877",
+ "reference": "8c2e9d22806acd5522691074e215bb0b04926877",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/inflector": "^4.4|^5.0"
+ },
+ "conflict": {
+ "phpdocumentor/reflection-docblock": "<3.2.2",
+ "phpdocumentor/type-resolver": "<0.3.0",
+ "symfony/dependency-injection": "<4.4"
+ },
+ "require-dev": {
+ "doctrine/annotations": "~1.7",
+ "phpdocumentor/reflection-docblock": "^3.0|^4.0",
+ "symfony/cache": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/serializer": "^4.4|^5.0"
+ },
+ "suggest": {
+ "phpdocumentor/reflection-docblock": "To use the PHPDoc",
+ "psr/cache-implementation": "To cache results",
+ "symfony/doctrine-bridge": "To use Doctrine metadata",
+ "symfony/serializer": "To use Serializer metadata"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\PropertyInfo\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Kévin Dunglas",
+ "email": "dunglas@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Property Info Component",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "doctrine",
+ "phpdoc",
+ "property",
+ "symfony",
+ "type",
+ "validator"
+ ],
+ "time": "2020-01-04T14:08:26+00:00"
+ },
+ {
+ "name": "symfony/routing",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/routing.git",
+ "reference": "d6ca39fd05c1902bf34d724ba06fb8044a0b46de"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/d6ca39fd05c1902bf34d724ba06fb8044a0b46de",
+ "reference": "d6ca39fd05c1902bf34d724ba06fb8044a0b46de",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5"
+ },
+ "conflict": {
+ "symfony/config": "<5.0",
+ "symfony/dependency-injection": "<4.4",
+ "symfony/yaml": "<4.4"
+ },
+ "require-dev": {
+ "doctrine/annotations": "~1.2",
+ "psr/log": "~1.0",
+ "symfony/config": "^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/yaml": "^4.4|^5.0"
+ },
+ "suggest": {
+ "doctrine/annotations": "For using the annotation loader",
+ "symfony/config": "For using the all-in-one router or any loader",
+ "symfony/expression-language": "For using expression matching",
+ "symfony/http-foundation": "For using a Symfony Request object",
+ "symfony/yaml": "For using the YAML loader"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Routing\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Routing Component",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "router",
+ "routing",
+ "uri",
+ "url"
+ ],
+ "time": "2020-02-25T14:24:11+00:00"
+ },
+ {
+ "name": "symfony/security-bundle",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/security-bundle.git",
+ "reference": "bbf735c1ea1778327a33c7fdadc3308a60667d74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/security-bundle/zipball/bbf735c1ea1778327a33c7fdadc3308a60667d74",
+ "reference": "bbf735c1ea1778327a33c7fdadc3308a60667d74",
+ "shasum": ""
+ },
+ "require": {
+ "ext-xml": "*",
+ "php": "^7.2.5",
+ "symfony/config": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/http-kernel": "^5.0",
+ "symfony/security-core": "^4.4|^5.0",
+ "symfony/security-csrf": "^4.4|^5.0",
+ "symfony/security-guard": "^4.4|^5.0",
+ "symfony/security-http": "^4.4.5|^5.0.5"
+ },
+ "conflict": {
+ "symfony/browser-kit": "<4.4",
+ "symfony/console": "<4.4",
+ "symfony/framework-bundle": "<4.4",
+ "symfony/ldap": "<4.4",
+ "symfony/twig-bundle": "<4.4"
+ },
+ "require-dev": {
+ "doctrine/doctrine-bundle": "^2.0",
+ "symfony/asset": "^4.4|^5.0",
+ "symfony/browser-kit": "^4.4|^5.0",
+ "symfony/console": "^4.4|^5.0",
+ "symfony/css-selector": "^4.4|^5.0",
+ "symfony/dom-crawler": "^4.4|^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/form": "^4.4|^5.0",
+ "symfony/framework-bundle": "^4.4|^5.0",
+ "symfony/process": "^4.4|^5.0",
+ "symfony/serializer": "^4.4|^5.0",
+ "symfony/translation": "^4.4|^5.0",
+ "symfony/twig-bridge": "^4.4|^5.0",
+ "symfony/twig-bundle": "^4.4|^5.0",
+ "symfony/validator": "^4.4|^5.0",
+ "symfony/yaml": "^4.4|^5.0",
+ "twig/twig": "^2.10|^3.0"
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bundle\\SecurityBundle\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony SecurityBundle",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-26T10:31:10+00:00"
+ },
+ {
+ "name": "symfony/security-core",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/security-core.git",
+ "reference": "2dfbd23f45e07d41e3ba94236924813b47f4fad6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/security-core/zipball/2dfbd23f45e07d41e3ba94236924813b47f4fad6",
+ "reference": "2dfbd23f45e07d41e3ba94236924813b47f4fad6",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/event-dispatcher-contracts": "^1.1|^2",
+ "symfony/service-contracts": "^1.1.6|^2"
+ },
+ "conflict": {
+ "symfony/event-dispatcher": "<4.4",
+ "symfony/ldap": "<4.4",
+ "symfony/security-guard": "<4.4"
+ },
+ "require-dev": {
+ "psr/container": "^1.0",
+ "psr/log": "~1.0",
+ "symfony/event-dispatcher": "^4.4|^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/ldap": "^4.4|^5.0",
+ "symfony/validator": "^4.4|^5.0"
+ },
+ "suggest": {
+ "psr/container-implementation": "To instantiate the Security class",
+ "symfony/event-dispatcher": "",
+ "symfony/expression-language": "For using the expression voter",
+ "symfony/http-foundation": "",
+ "symfony/ldap": "For using LDAP integration",
+ "symfony/validator": "For using the user password constraint"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Security\\Core\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Security Component - Core Library",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-24T15:05:31+00:00"
+ },
+ {
+ "name": "symfony/security-csrf",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/security-csrf.git",
+ "reference": "65066f7e0f6e38a8c5507c706e86e7a52fd7ff3e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/security-csrf/zipball/65066f7e0f6e38a8c5507c706e86e7a52fd7ff3e",
+ "reference": "65066f7e0f6e38a8c5507c706e86e7a52fd7ff3e",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/security-core": "^4.4|^5.0"
+ },
+ "conflict": {
+ "symfony/http-foundation": "<4.4"
+ },
+ "require-dev": {
+ "symfony/http-foundation": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/http-foundation": "For using the class SessionTokenStorage."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Security\\Csrf\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Security Component - CSRF Library",
+ "homepage": "https://symfony.com",
+ "time": "2020-01-04T14:08:26+00:00"
+ },
+ {
+ "name": "symfony/security-guard",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/security-guard.git",
+ "reference": "8a8d4006061c59010e0b6b94b6a7803b61bf875d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/security-guard/zipball/8a8d4006061c59010e0b6b94b6a7803b61bf875d",
+ "reference": "8a8d4006061c59010e0b6b94b6a7803b61bf875d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/security-core": "^5.0",
+ "symfony/security-http": "^4.4.1|^5.0.1"
+ },
+ "require-dev": {
+ "psr/log": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Security\\Guard\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Security Component - Guard",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-24T15:05:31+00:00"
+ },
+ {
+ "name": "symfony/security-http",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/security-http.git",
+ "reference": "4d2b2d9b5e602747bde8937e01aee535f6ae2ec2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/security-http/zipball/4d2b2d9b5e602747bde8937e01aee535f6ae2ec2",
+ "reference": "4d2b2d9b5e602747bde8937e01aee535f6ae2ec2",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/http-kernel": "^4.4|^5.0",
+ "symfony/property-access": "^4.4|^5.0",
+ "symfony/security-core": "^4.4|^5.0"
+ },
+ "conflict": {
+ "symfony/security-csrf": "<4.4"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/routing": "^4.4|^5.0",
+ "symfony/security-csrf": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs",
+ "symfony/security-csrf": "For using tokens to protect authentication/logout attempts"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Security\\Http\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Security Component - HTTP Integration",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-26T10:31:10+00:00"
+ },
+ {
+ "name": "symfony/serializer",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/serializer.git",
+ "reference": "4411e7356beda717880da28cdbd32b33c52bb894"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/serializer/zipball/4411e7356beda717880da28cdbd32b33c52bb894",
+ "reference": "4411e7356beda717880da28cdbd32b33c52bb894",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "conflict": {
+ "phpdocumentor/type-resolver": "<0.2.1",
+ "symfony/dependency-injection": "<4.4",
+ "symfony/property-access": "<4.4",
+ "symfony/property-info": "<4.4",
+ "symfony/yaml": "<4.4"
+ },
+ "require-dev": {
+ "doctrine/annotations": "~1.0",
+ "doctrine/cache": "~1.0",
+ "phpdocumentor/reflection-docblock": "^3.2|^4.0",
+ "symfony/cache": "^4.4|^5.0",
+ "symfony/config": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/error-handler": "^4.4|^5.0",
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/mime": "^4.4|^5.0",
+ "symfony/property-access": "^4.4|^5.0",
+ "symfony/property-info": "^4.4|^5.0",
+ "symfony/validator": "^4.4|^5.0",
+ "symfony/yaml": "^4.4|^5.0"
+ },
+ "suggest": {
+ "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.",
+ "doctrine/cache": "For using the default cached annotation reader and metadata cache.",
+ "psr/cache-implementation": "For using the metadata cache.",
+ "symfony/config": "For using the XML mapping loader.",
+ "symfony/mime": "For using a MIME type guesser within the DataUriNormalizer.",
+ "symfony/property-access": "For using the ObjectNormalizer.",
+ "symfony/property-info": "To deserialize relations.",
+ "symfony/yaml": "For using the default YAML mapping loader."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Serializer\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Serializer Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-29T10:07:09+00:00"
+ },
+ {
+ "name": "symfony/serializer-pack",
+ "version": "v1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/serializer-pack.git",
+ "reference": "c5f18ba4ff989a42d7d140b7f85406e77cd8c4b2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/serializer-pack/zipball/c5f18ba4ff989a42d7d140b7f85406e77cd8c4b2",
+ "reference": "c5f18ba4ff989a42d7d140b7f85406e77cd8c4b2",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/annotations": "^1.0",
+ "php": "^7.0",
+ "phpdocumentor/reflection-docblock": "^3.0|^4.0",
+ "symfony/property-access": "*",
+ "symfony/property-info": "*",
+ "symfony/serializer": "*"
+ },
+ "type": "symfony-pack",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A pack for the Symfony serializer",
+ "time": "2018-12-10T12:14:14+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "144c5e51266b281231e947b51223ba14acf1a749"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749",
+ "reference": "144c5e51266b281231e947b51223ba14acf1a749",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "psr/container": "^1.0"
+ },
+ "suggest": {
+ "symfony/service-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "time": "2019-11-18T17:27:11+00:00"
+ },
+ {
+ "name": "symfony/stopwatch",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/stopwatch.git",
+ "reference": "5d9add8034135b9a5f7b101d1e42c797e7f053e4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5d9add8034135b9a5f7b101d1e42c797e7f053e4",
+ "reference": "5d9add8034135b9a5f7b101d1e42c797e7f053e4",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/service-contracts": "^1.0|^2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Stopwatch\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Stopwatch Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-01-04T14:08:26+00:00"
+ },
+ {
+ "name": "symfony/string",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/string.git",
+ "reference": "a45ae78382337833e3b0ab3097d1769074950007"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/string/zipball/a45ae78382337833e3b0ab3097d1769074950007",
+ "reference": "a45ae78382337833e3b0ab3097d1769074950007",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/polyfill-intl-grapheme": "~1.0",
+ "symfony/polyfill-intl-normalizer": "~1.0",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/translation-contracts": "^1.1|^2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\String\\": ""
+ },
+ "files": [
+ "Resources/functions.php"
+ ],
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony String component",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "grapheme",
+ "i18n",
+ "string",
+ "unicode",
+ "utf-8",
+ "utf8"
+ ],
+ "time": "2020-02-26T22:30:10+00:00"
+ },
+ {
+ "name": "symfony/translation",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/translation.git",
+ "reference": "e9b93f42a1fd6aec6a0872d59ee5c8219a7d584b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/e9b93f42a1fd6aec6a0872d59ee5c8219a7d584b",
+ "reference": "e9b93f42a1fd6aec6a0872d59ee5c8219a7d584b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/translation-contracts": "^2"
+ },
+ "conflict": {
+ "symfony/config": "<4.4",
+ "symfony/dependency-injection": "<5.0",
+ "symfony/http-kernel": "<5.0",
+ "symfony/twig-bundle": "<5.0",
+ "symfony/yaml": "<4.4"
+ },
+ "provide": {
+ "symfony/translation-implementation": "2.0"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "^4.4|^5.0",
+ "symfony/console": "^4.4|^5.0",
+ "symfony/dependency-injection": "^5.0",
+ "symfony/finder": "^4.4|^5.0",
+ "symfony/http-kernel": "^5.0",
+ "symfony/intl": "^4.4|^5.0",
+ "symfony/service-contracts": "^1.1.2|^2",
+ "symfony/yaml": "^4.4|^5.0"
+ },
+ "suggest": {
+ "psr/log-implementation": "To use logging capability in translator",
+ "symfony/config": "",
+ "symfony/yaml": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Translation\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Translation Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-04T07:41:34+00:00"
+ },
+ {
+ "name": "symfony/translation-contracts",
+ "version": "v2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/translation-contracts.git",
+ "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/8cc682ac458d75557203b2f2f14b0b92e1c744ed",
+ "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5"
+ },
+ "suggest": {
+ "symfony/translation-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Translation\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to translation",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "time": "2019-11-18T17:27:11+00:00"
+ },
+ {
+ "name": "symfony/twig-bridge",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/twig-bridge.git",
+ "reference": "737eeafbd04bf057c9495327c5d2669be7b79ba9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/737eeafbd04bf057c9495327c5d2669be7b79ba9",
+ "reference": "737eeafbd04bf057c9495327c5d2669be7b79ba9",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/translation-contracts": "^1.1|^2",
+ "twig/twig": "^2.10|^3.0"
+ },
+ "conflict": {
+ "symfony/console": "<4.4",
+ "symfony/form": "<5.0",
+ "symfony/http-foundation": "<4.4",
+ "symfony/http-kernel": "<4.4",
+ "symfony/translation": "<5.0",
+ "symfony/workflow": "<4.4"
+ },
+ "require-dev": {
+ "egulias/email-validator": "^2.1.10",
+ "symfony/asset": "^4.4|^5.0",
+ "symfony/console": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/finder": "^4.4|^5.0",
+ "symfony/form": "^5.0",
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/http-kernel": "^4.4|^5.0",
+ "symfony/mime": "^4.4|^5.0",
+ "symfony/polyfill-intl-icu": "~1.0",
+ "symfony/routing": "^4.4|^5.0",
+ "symfony/security-acl": "^2.8|^3.0",
+ "symfony/security-core": "^4.4|^5.0",
+ "symfony/security-csrf": "^4.4|^5.0",
+ "symfony/security-http": "^4.4|^5.0",
+ "symfony/stopwatch": "^4.4|^5.0",
+ "symfony/translation": "^5.0",
+ "symfony/web-link": "^4.4|^5.0",
+ "symfony/workflow": "^4.4|^5.0",
+ "symfony/yaml": "^4.4|^5.0",
+ "twig/cssinliner-extra": "^2.12",
+ "twig/inky-extra": "^2.12",
+ "twig/markdown-extra": "^2.12"
+ },
+ "suggest": {
+ "symfony/asset": "For using the AssetExtension",
+ "symfony/expression-language": "For using the ExpressionExtension",
+ "symfony/finder": "",
+ "symfony/form": "For using the FormExtension",
+ "symfony/http-kernel": "For using the HttpKernelExtension",
+ "symfony/routing": "For using the RoutingExtension",
+ "symfony/security-core": "For using the SecurityExtension",
+ "symfony/security-csrf": "For using the CsrfExtension",
+ "symfony/security-http": "For using the LogoutUrlExtension",
+ "symfony/stopwatch": "For using the StopwatchExtension",
+ "symfony/translation": "For using the TranslationExtension",
+ "symfony/var-dumper": "For using the DumpExtension",
+ "symfony/web-link": "For using the WebLinkExtension",
+ "symfony/yaml": "For using the YamlExtension"
+ },
+ "type": "symfony-bridge",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bridge\\Twig\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Twig Bridge",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-24T15:05:31+00:00"
+ },
+ {
+ "name": "symfony/twig-bundle",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/twig-bundle.git",
+ "reference": "7a3e2b4fc7969168d5502aa551404c500aa79891"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/7a3e2b4fc7969168d5502aa551404c500aa79891",
+ "reference": "7a3e2b4fc7969168d5502aa551404c500aa79891",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/config": "^4.4|^5.0",
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/http-kernel": "^5.0",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/twig-bridge": "^5.0",
+ "twig/twig": "^2.10|^3.0"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<4.4",
+ "symfony/framework-bundle": "<5.0",
+ "symfony/translation": "<5.0"
+ },
+ "require-dev": {
+ "doctrine/annotations": "~1.7",
+ "doctrine/cache": "~1.0",
+ "symfony/asset": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/finder": "^4.4|^5.0",
+ "symfony/form": "^4.4|^5.0",
+ "symfony/framework-bundle": "^5.0",
+ "symfony/routing": "^4.4|^5.0",
+ "symfony/stopwatch": "^4.4|^5.0",
+ "symfony/translation": "^5.0",
+ "symfony/web-link": "^4.4|^5.0",
+ "symfony/yaml": "^4.4|^5.0"
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bundle\\TwigBundle\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony TwigBundle",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-04T09:47:34+00:00"
+ },
+ {
+ "name": "symfony/twig-pack",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/twig-pack.git",
+ "reference": "8b278ea4b61fba7c051f172aacae6d87ef4be0e0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/twig-pack/zipball/8b278ea4b61fba7c051f172aacae6d87ef4be0e0",
+ "reference": "8b278ea4b61fba7c051f172aacae6d87ef4be0e0",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "symfony/twig-bundle": "*",
+ "twig/extra-bundle": "^2.12|^3.0",
+ "twig/twig": "^2.12|^3.0"
+ },
+ "type": "symfony-pack",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A Twig pack for Symfony projects",
+ "time": "2019-10-17T05:44:22+00:00"
+ },
+ {
+ "name": "symfony/validator",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/validator.git",
+ "reference": "fb9c52b2fe3a8336b65f85b61dedbcc6c427c37b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/validator/zipball/fb9c52b2fe3a8336b65f85b61dedbcc6c427c37b",
+ "reference": "fb9c52b2fe3a8336b65f85b61dedbcc6c427c37b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/translation-contracts": "^1.1|^2"
+ },
+ "conflict": {
+ "doctrine/lexer": "<1.0.2",
+ "phpunit/phpunit": "<5.4.3",
+ "symfony/dependency-injection": "<4.4",
+ "symfony/http-kernel": "<4.4",
+ "symfony/intl": "<4.4",
+ "symfony/translation": "<4.4",
+ "symfony/yaml": "<4.4"
+ },
+ "require-dev": {
+ "doctrine/annotations": "~1.7",
+ "doctrine/cache": "~1.0",
+ "egulias/email-validator": "^2.1.10",
+ "symfony/cache": "^4.4|^5.0",
+ "symfony/config": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/http-client": "^4.4|^5.0",
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/http-kernel": "^4.4|^5.0",
+ "symfony/intl": "^4.4|^5.0",
+ "symfony/mime": "^4.4|^5.0",
+ "symfony/property-access": "^4.4|^5.0",
+ "symfony/property-info": "^4.4|^5.0",
+ "symfony/translation": "^4.4|^5.0",
+ "symfony/yaml": "^4.4|^5.0"
+ },
+ "suggest": {
+ "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.",
+ "doctrine/cache": "For using the default cached annotation reader.",
+ "egulias/email-validator": "Strict (RFC compliant) email validation",
+ "psr/cache-implementation": "For using the mapping cache.",
+ "symfony/config": "",
+ "symfony/expression-language": "For using the Expression validator",
+ "symfony/http-foundation": "",
+ "symfony/intl": "",
+ "symfony/property-access": "For accessing properties within comparison constraints",
+ "symfony/property-info": "To automatically add NotNull and Type constraints",
+ "symfony/translation": "For translating validation errors.",
+ "symfony/yaml": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Validator\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Validator Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-29T10:07:09+00:00"
+ },
+ {
+ "name": "symfony/var-dumper",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/var-dumper.git",
+ "reference": "3a37aeb1132d1035536d3d6aa9cb06c2ff9355e9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3a37aeb1132d1035536d3d6aa9cb06c2ff9355e9",
+ "reference": "3a37aeb1132d1035536d3d6aa9cb06c2ff9355e9",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<5.4.3",
+ "symfony/console": "<4.4"
+ },
+ "require-dev": {
+ "ext-iconv": "*",
+ "symfony/console": "^4.4|^5.0",
+ "symfony/process": "^4.4|^5.0",
+ "twig/twig": "^2.4|^3.0"
+ },
+ "suggest": {
+ "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
+ "ext-intl": "To show region name in time zone dump",
+ "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
+ },
+ "bin": [
+ "Resources/bin/var-dump-server"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "Resources/functions/dump.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\VarDumper\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony mechanism for exploring and dumping PHP variables",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "debug",
+ "dump"
+ ],
+ "time": "2020-02-26T22:30:10+00:00"
+ },
+ {
+ "name": "symfony/var-exporter",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/var-exporter.git",
+ "reference": "30779a25c736b4290449eaedefe4196c1d060378"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/var-exporter/zipball/30779a25c736b4290449eaedefe4196c1d060378",
+ "reference": "30779a25c736b4290449eaedefe4196c1d060378",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5"
+ },
+ "require-dev": {
+ "symfony/var-dumper": "^4.4|^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\VarExporter\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "clone",
+ "construct",
+ "export",
+ "hydrate",
+ "instantiate",
+ "serialize"
+ ],
+ "time": "2020-02-04T09:47:34+00:00"
+ },
+ {
+ "name": "symfony/web-link",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/web-link.git",
+ "reference": "78dd64d9f666550f4f7d9e64b59337e7f274389d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/web-link/zipball/78dd64d9f666550f4f7d9e64b59337e7f274389d",
+ "reference": "78dd64d9f666550f4f7d9e64b59337e7f274389d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "psr/link": "^1.0"
+ },
+ "conflict": {
+ "symfony/http-kernel": "<4.4"
+ },
+ "provide": {
+ "psr/link-implementation": "1.0"
+ },
+ "require-dev": {
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/http-kernel": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/http-kernel": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\WebLink\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Kévin Dunglas",
+ "email": "dunglas@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony WebLink Component",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "dns-prefetch",
+ "http",
+ "http2",
+ "link",
+ "performance",
+ "prefetch",
+ "preload",
+ "prerender",
+ "psr13",
+ "push"
+ ],
+ "time": "2020-01-04T14:08:26+00:00"
+ },
+ {
+ "name": "symfony/yaml",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/yaml.git",
+ "reference": "a4b613d7e44f62941adff5a802cff70adee57d3f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/a4b613d7e44f62941adff5a802cff70adee57d3f",
+ "reference": "a4b613d7e44f62941adff5a802cff70adee57d3f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "conflict": {
+ "symfony/console": "<4.4"
+ },
+ "require-dev": {
+ "symfony/console": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/console": "For validating YAML files using the lint command"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Yaml\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Yaml Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-03T13:51:17+00:00"
+ },
+ {
+ "name": "twig/extra-bundle",
+ "version": "v3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/twigphp/twig-extra-bundle.git",
+ "reference": "6eaf1637abe6b68518e7e0949ebb84e55770d5c6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/6eaf1637abe6b68518e7e0949ebb84e55770d5c6",
+ "reference": "6eaf1637abe6b68518e7e0949ebb84e55770d5c6",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3",
+ "symfony/framework-bundle": "^4.3|^5.0",
+ "symfony/twig-bundle": "^4.3|^5.0",
+ "twig/twig": "^2.4|^3.0"
+ },
+ "require-dev": {
+ "twig/cssinliner-extra": "^2.12|^3.0",
+ "twig/html-extra": "^2.12|^3.0",
+ "twig/inky-extra": "^2.12|^3.0",
+ "twig/intl-extra": "^2.12|^3.0",
+ "twig/markdown-extra": "^2.12|^3.0"
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Twig\\Extra\\TwigExtraBundle\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com",
+ "homepage": "http://fabien.potencier.org",
+ "role": "Lead Developer"
+ }
+ ],
+ "description": "A Symfony bundle for extra Twig extensions",
+ "homepage": "https://twig.symfony.com",
+ "keywords": [
+ "bundle",
+ "extra",
+ "twig"
+ ],
+ "time": "2020-01-01T17:11:09+00:00"
+ },
+ {
+ "name": "twig/twig",
+ "version": "v3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/twigphp/Twig.git",
+ "reference": "3b88ccd180a6b61ebb517aea3b1a8906762a1dc2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/3b88ccd180a6b61ebb517aea3b1a8906762a1dc2",
+ "reference": "3b88ccd180a6b61ebb517aea3b1a8906762a1dc2",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/polyfill-ctype": "^1.8",
+ "symfony/polyfill-mbstring": "^1.3"
+ },
+ "require-dev": {
+ "psr/container": "^1.0",
+ "symfony/phpunit-bridge": "^4.4|^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Twig\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com",
+ "homepage": "http://fabien.potencier.org",
+ "role": "Lead Developer"
+ },
+ {
+ "name": "Twig Team",
+ "role": "Contributors"
+ },
+ {
+ "name": "Armin Ronacher",
+ "email": "armin.ronacher@active-4.com",
+ "role": "Project Founder"
+ }
+ ],
+ "description": "Twig, the flexible, fast, and secure template language for PHP",
+ "homepage": "https://twig.symfony.com",
+ "keywords": [
+ "templating"
+ ],
+ "time": "2020-02-11T15:33:47+00:00"
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "1.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozart/assert.git",
+ "reference": "aed98a490f9a8f78468232db345ab9cf606cf598"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598",
+ "reference": "aed98a490f9a8f78468232db345ab9cf606cf598",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.3 || ^7.0",
+ "symfony/polyfill-ctype": "^1.8"
+ },
+ "conflict": {
+ "vimeo/psalm": "<3.6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.36 || ^7.5.13"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ],
+ "time": "2020-02-14T12:15:55+00:00"
+ },
+ {
+ "name": "zendframework/zend-code",
+ "version": "3.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/zendframework/zend-code.git",
+ "reference": "268040548f92c2bfcba164421c1add2ba43abaaa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/zendframework/zend-code/zipball/268040548f92c2bfcba164421c1add2ba43abaaa",
+ "reference": "268040548f92c2bfcba164421c1add2ba43abaaa",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1",
+ "zendframework/zend-eventmanager": "^2.6 || ^3.0"
+ },
+ "conflict": {
+ "phpspec/prophecy": "<1.9.0"
+ },
+ "require-dev": {
+ "doctrine/annotations": "^1.7",
+ "ext-phar": "*",
+ "phpunit/phpunit": "^7.5.16 || ^8.4",
+ "zendframework/zend-coding-standard": "^1.0",
+ "zendframework/zend-stdlib": "^2.7 || ^3.0"
+ },
+ "suggest": {
+ "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features",
+ "zendframework/zend-stdlib": "Zend\\Stdlib component"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4.x-dev",
+ "dev-develop": "3.5.x-dev",
+ "dev-dev-4.0": "4.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Zend\\Code\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "Extensions to the PHP Reflection API, static code scanning, and code generation",
+ "keywords": [
+ "ZendFramework",
+ "code",
+ "zf"
+ ],
+ "abandoned": "laminas/laminas-code",
+ "time": "2019-12-10T19:21:15+00:00"
+ },
+ {
+ "name": "zendframework/zend-eventmanager",
+ "version": "3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/zendframework/zend-eventmanager.git",
+ "reference": "a5e2583a211f73604691586b8406ff7296a946dd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/a5e2583a211f73604691586b8406ff7296a946dd",
+ "reference": "a5e2583a211f73604691586b8406ff7296a946dd",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0"
+ },
+ "require-dev": {
+ "athletic/athletic": "^0.1",
+ "container-interop/container-interop": "^1.1.0",
+ "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2",
+ "zendframework/zend-coding-standard": "~1.0.0",
+ "zendframework/zend-stdlib": "^2.7.3 || ^3.0"
+ },
+ "suggest": {
+ "container-interop/container-interop": "^1.1.0, to use the lazy listeners feature",
+ "zendframework/zend-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2-dev",
+ "dev-develop": "3.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Zend\\EventManager\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "Trigger and listen to events within a PHP application",
+ "homepage": "https://github.com/zendframework/zend-eventmanager",
+ "keywords": [
+ "event",
+ "eventmanager",
+ "events",
+ "zf2"
+ ],
+ "abandoned": "laminas/laminas-eventmanager",
+ "time": "2018-04-25T15:33:34+00:00"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "easycorp/easy-log-handler",
+ "version": "v1.0.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/EasyCorp/easy-log-handler.git",
+ "reference": "224e1dfcf9455aceee89cd0af306ac097167fac1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/EasyCorp/easy-log-handler/zipball/224e1dfcf9455aceee89cd0af306ac097167fac1",
+ "reference": "224e1dfcf9455aceee89cd0af306ac097167fac1",
+ "shasum": ""
+ },
+ "require": {
+ "monolog/monolog": "~1.6|~2.0",
+ "php": ">=7.1",
+ "symfony/yaml": "^3.4|^4.0|^5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "EasyCorp\\EasyLog\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Javier Eguiluz",
+ "email": "javiereguiluz@gmail.com"
+ },
+ {
+ "name": "Project Contributors",
+ "homepage": "https://github.com/EasyCorp/easy-log-handler"
+ }
+ ],
+ "description": "A handler for Monolog that optimizes log messages to be processed by humans instead of software. Improve your productivity with logs that are easy to understand.",
+ "homepage": "https://github.com/EasyCorp/easy-log-handler",
+ "keywords": [
+ "easy",
+ "log",
+ "logging",
+ "monolog",
+ "productivity"
+ ],
+ "time": "2019-10-24T07:13:31+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v4.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9a9981c347c5c49d6dfe5cf826bb882b824080dc",
+ "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "0.0.5",
+ "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "time": "2019-11-08T13:50:10+00:00"
+ },
+ {
+ "name": "symfony/browser-kit",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/browser-kit.git",
+ "reference": "6b2a9590a5868f0ce5cbf7af6abe563d1a3930a3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/browser-kit/zipball/6b2a9590a5868f0ce5cbf7af6abe563d1a3930a3",
+ "reference": "6b2a9590a5868f0ce5cbf7af6abe563d1a3930a3",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/dom-crawler": "^4.4|^5.0"
+ },
+ "require-dev": {
+ "symfony/css-selector": "^4.4|^5.0",
+ "symfony/http-client": "^4.4|^5.0",
+ "symfony/mime": "^4.4|^5.0",
+ "symfony/process": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/process": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\BrowserKit\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony BrowserKit Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-24T15:05:31+00:00"
+ },
+ {
+ "name": "symfony/css-selector",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/css-selector.git",
+ "reference": "a0b51ba9938ccc206d9284de7eb527c2d4550b44"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/a0b51ba9938ccc206d9284de7eb527c2d4550b44",
+ "reference": "a0b51ba9938ccc206d9284de7eb527c2d4550b44",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\CssSelector\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Jean-François Simon",
+ "email": "jeanfrancois.simon@sensiolabs.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony CssSelector Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-04T09:41:09+00:00"
+ },
+ {
+ "name": "symfony/debug-bundle",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/debug-bundle.git",
+ "reference": "1f4d3b753f0a9effff115726ff2b5b6eaa800418"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/1f4d3b753f0a9effff115726ff2b5b6eaa800418",
+ "reference": "1f4d3b753f0a9effff115726ff2b5b6eaa800418",
+ "shasum": ""
+ },
+ "require": {
+ "ext-xml": "*",
+ "php": "^7.2.5",
+ "symfony/http-kernel": "^4.4|^5.0",
+ "symfony/twig-bridge": "^4.4|^5.0",
+ "symfony/var-dumper": "^4.4|^5.0"
+ },
+ "conflict": {
+ "symfony/config": "<4.4",
+ "symfony/dependency-injection": "<4.4"
+ },
+ "require-dev": {
+ "symfony/config": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/web-profiler-bundle": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/config": "For service container configuration",
+ "symfony/dependency-injection": "For using as a service from the container"
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bundle\\DebugBundle\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony DebugBundle",
+ "homepage": "https://symfony.com",
+ "time": "2020-01-04T14:08:26+00:00"
+ },
+ {
+ "name": "symfony/debug-pack",
+ "version": "v1.0.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/debug-pack.git",
+ "reference": "09a4a1e9bf2465987d4f79db0ad6c11cc632bc79"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/debug-pack/zipball/09a4a1e9bf2465987d4f79db0ad6c11cc632bc79",
+ "reference": "09a4a1e9bf2465987d4f79db0ad6c11cc632bc79",
+ "shasum": ""
+ },
+ "require": {
+ "easycorp/easy-log-handler": "^1.0.7",
+ "php": "^7.0",
+ "symfony/debug-bundle": "*",
+ "symfony/monolog-bundle": "^3.0",
+ "symfony/profiler-pack": "*",
+ "symfony/var-dumper": "*"
+ },
+ "type": "symfony-pack",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A debug pack for Symfony projects",
+ "time": "2018-12-10T12:11:11+00:00"
+ },
+ {
+ "name": "symfony/dom-crawler",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/dom-crawler.git",
+ "reference": "4368bdd61b83af365b8f23e9616d2a2ed52cbe7c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/4368bdd61b83af365b8f23e9616d2a2ed52cbe7c",
+ "reference": "4368bdd61b83af365b8f23e9616d2a2ed52cbe7c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "masterminds/html5": "<2.6"
+ },
+ "require-dev": {
+ "masterminds/html5": "^2.6",
+ "symfony/css-selector": "^4.4|^5.0"
+ },
+ "suggest": {
+ "symfony/css-selector": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\DomCrawler\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony DomCrawler Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-29T10:07:09+00:00"
+ },
+ {
+ "name": "symfony/maker-bundle",
+ "version": "v1.14.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/maker-bundle.git",
+ "reference": "bc4df88792fbaaeb275167101dc714218475db5f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/bc4df88792fbaaeb275167101dc714218475db5f",
+ "reference": "bc4df88792fbaaeb275167101dc714218475db5f",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/inflector": "^1.2",
+ "nikic/php-parser": "^4.0",
+ "php": "^7.0.8",
+ "symfony/config": "^3.4|^4.0|^5.0",
+ "symfony/console": "^3.4|^4.0|^5.0",
+ "symfony/dependency-injection": "^3.4|^4.0|^5.0",
+ "symfony/filesystem": "^3.4|^4.0|^5.0",
+ "symfony/finder": "^3.4|^4.0|^5.0",
+ "symfony/framework-bundle": "^3.4|^4.0|^5.0",
+ "symfony/http-kernel": "^3.4|^4.0|^5.0"
+ },
+ "require-dev": {
+ "doctrine/doctrine-bundle": "^1.8|^2.0",
+ "doctrine/orm": "^2.3",
+ "friendsofphp/php-cs-fixer": "^2.8",
+ "friendsoftwig/twigcs": "^3.1.2",
+ "symfony/http-client": "^4.3|^5.0",
+ "symfony/phpunit-bridge": "^4.3|^5.0",
+ "symfony/process": "^3.4|^4.0|^5.0",
+ "symfony/security-core": "^3.4|^4.0|^5.0",
+ "symfony/yaml": "^3.4|^4.0|^5.0"
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bundle\\MakerBundle\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Maker helps you create empty commands, controllers, form classes, tests and more so you can forget about writing boilerplate code.",
+ "homepage": "https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html",
+ "keywords": [
+ "code generator",
+ "generator",
+ "scaffold",
+ "scaffolding"
+ ],
+ "time": "2020-03-04T13:57:29+00:00"
+ },
+ {
+ "name": "symfony/phpunit-bridge",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/phpunit-bridge.git",
+ "reference": "b8fee53045a55ccbb9209e453bf6fdcf74381959"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/b8fee53045a55ccbb9209e453bf6fdcf74381959",
+ "reference": "b8fee53045a55ccbb9209e453bf6fdcf74381959",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0|<6.4,>=6.0"
+ },
+ "suggest": {
+ "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader"
+ },
+ "bin": [
+ "bin/simple-phpunit"
+ ],
+ "type": "symfony-bridge",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ },
+ "thanks": {
+ "name": "phpunit/phpunit",
+ "url": "https://github.com/sebastianbergmann/phpunit"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Bridge\\PhpUnit\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony PHPUnit Bridge",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-24T15:05:31+00:00"
+ },
+ {
+ "name": "symfony/profiler-pack",
+ "version": "v1.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/profiler-pack.git",
+ "reference": "99c4370632c2a59bb0444852f92140074ef02209"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/profiler-pack/zipball/99c4370632c2a59bb0444852f92140074ef02209",
+ "reference": "99c4370632c2a59bb0444852f92140074ef02209",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "symfony/stopwatch": "*",
+ "symfony/twig-bundle": "*",
+ "symfony/web-profiler-bundle": "*"
+ },
+ "type": "symfony-pack",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A pack for the Symfony web profiler",
+ "time": "2018-12-10T12:11:44+00:00"
+ },
+ {
+ "name": "symfony/test-pack",
+ "version": "v1.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/test-pack.git",
+ "reference": "ff87e800a67d06c423389f77b8209bc9dc469def"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/test-pack/zipball/ff87e800a67d06c423389f77b8209bc9dc469def",
+ "reference": "ff87e800a67d06c423389f77b8209bc9dc469def",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "symfony/browser-kit": "*",
+ "symfony/css-selector": "*",
+ "symfony/phpunit-bridge": "*"
+ },
+ "type": "symfony-pack",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A pack for functional and end-to-end testing within a Symfony app",
+ "time": "2019-06-21T06:27:32+00:00"
+ },
+ {
+ "name": "symfony/web-profiler-bundle",
+ "version": "v5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/web-profiler-bundle.git",
+ "reference": "209b76b879fee706fecbd8ad2113d810322ab62a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/209b76b879fee706fecbd8ad2113d810322ab62a",
+ "reference": "209b76b879fee706fecbd8ad2113d810322ab62a",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "symfony/config": "^4.4|^5.0",
+ "symfony/framework-bundle": "^4.4|^5.0",
+ "symfony/http-kernel": "^4.4|^5.0",
+ "symfony/routing": "^4.4|^5.0",
+ "symfony/twig-bundle": "^4.4|^5.0",
+ "twig/twig": "^2.10|^3.0"
+ },
+ "conflict": {
+ "symfony/form": "<4.4",
+ "symfony/messenger": "<4.4"
+ },
+ "require-dev": {
+ "symfony/browser-kit": "^4.4|^5.0",
+ "symfony/console": "^4.4|^5.0",
+ "symfony/css-selector": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/stopwatch": "^4.4|^5.0"
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bundle\\WebProfilerBundle\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony WebProfilerBundle",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-14T07:43:07+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": "^7.2.5",
+ "ext-ctype": "*",
+ "ext-iconv": "*"
+ },
+ "platform-dev": []
+}
diff --git a/console/skel/symfony-app/config/bootstrap.php b/console/skel/symfony-app/config/bootstrap.php
new file mode 100644
index 0000000..3164fd1
--- /dev/null
+++ b/console/skel/symfony-app/config/bootstrap.php
@@ -0,0 +1,23 @@
+=1.2)
+if (is_array($env = @include dirname(__DIR__).'/.env.local.php') && (!isset($env['APP_ENV']) || ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? $env['APP_ENV']) === $env['APP_ENV'])) {
+ foreach ($env as $k => $v) {
+ $_ENV[$k] = $_ENV[$k] ?? (isset($_SERVER[$k]) && 0 !== strpos($k, 'HTTP_') ? $_SERVER[$k] : $v);
+ }
+} elseif (!class_exists(Dotenv::class)) {
+ throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.');
+} else {
+ // load all the .env files
+ (new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env');
+}
+
+$_SERVER += $_ENV;
+$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev';
+$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV'];
+$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0';
diff --git a/console/skel/symfony-app/config/bundles.php b/console/skel/symfony-app/config/bundles.php
new file mode 100644
index 0000000..025de07
--- /dev/null
+++ b/console/skel/symfony-app/config/bundles.php
@@ -0,0 +1,45 @@
+ ['all' => true],
+ Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
+ Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
+ Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
+ Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
+ Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
+ Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
+ Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
+ Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
+ Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
+ Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
+ /*App\Session\AuthBundle\SessionAuthBundle::class => ['all' => true],*/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+];
diff --git a/console/skel/symfony-app/config/packages/cache.yaml b/console/skel/symfony-app/config/packages/cache.yaml
new file mode 100644
index 0000000..6899b72
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/cache.yaml
@@ -0,0 +1,19 @@
+framework:
+ cache:
+ # Unique name of your app: used to compute stable namespaces for cache keys.
+ #prefix_seed: your_vendor_name/app_name
+
+ # The "app" cache stores to the filesystem by default.
+ # The data in this cache should persist between deploys.
+ # Other options include:
+
+ # Redis
+ #app: cache.adapter.redis
+ #default_redis_provider: redis://localhost
+
+ # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
+ #app: cache.adapter.apcu
+
+ # Namespaced pools use the above "app" backend by default
+ #pools:
+ #my.dedicated.cache: null
diff --git a/console/skel/symfony-app/config/packages/dev/debug.yaml b/console/skel/symfony-app/config/packages/dev/debug.yaml
new file mode 100644
index 0000000..26d4e53
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/dev/debug.yaml
@@ -0,0 +1,4 @@
+debug:
+ # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
+ # See the "server:dump" command to start a new server.
+ dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
diff --git a/console/skel/symfony-app/config/packages/dev/easy_log_handler.yaml b/console/skel/symfony-app/config/packages/dev/easy_log_handler.yaml
new file mode 100644
index 0000000..27bfc60
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/dev/easy_log_handler.yaml
@@ -0,0 +1,16 @@
+services:
+ EasyCorp\EasyLog\EasyLogHandler:
+ public: false
+ arguments: ['%kernel.logs_dir%/%kernel.environment%.log']
+
+#// FIXME: How to add this configuration automatically without messing up with the monolog configuration?
+#monolog:
+# handlers:
+# buffered:
+# type: buffer
+# handler: easylog
+# channels: ['!event']
+# level: debug
+# easylog:
+# type: service
+# id: EasyCorp\EasyLog\EasyLogHandler
diff --git a/console/skel/symfony-app/config/packages/dev/monolog.yaml b/console/skel/symfony-app/config/packages/dev/monolog.yaml
new file mode 100644
index 0000000..b1998da
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/dev/monolog.yaml
@@ -0,0 +1,19 @@
+monolog:
+ handlers:
+ main:
+ type: stream
+ path: "%kernel.logs_dir%/%kernel.environment%.log"
+ level: debug
+ channels: ["!event"]
+ # uncomment to get logging in your browser
+ # you may have to allow bigger header sizes in your Web server configuration
+ #firephp:
+ # type: firephp
+ # level: info
+ #chromephp:
+ # type: chromephp
+ # level: info
+ console:
+ type: console
+ process_psr_3_messages: false
+ channels: ["!event", "!doctrine", "!console"]
diff --git a/console/skel/symfony-app/config/packages/dev/web_profiler.yaml b/console/skel/symfony-app/config/packages/dev/web_profiler.yaml
new file mode 100644
index 0000000..e92166a
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/dev/web_profiler.yaml
@@ -0,0 +1,6 @@
+web_profiler:
+ toolbar: true
+ intercept_redirects: false
+
+framework:
+ profiler: { only_exceptions: false }
diff --git a/console/skel/symfony-app/config/packages/doctrine.yaml b/console/skel/symfony-app/config/packages/doctrine.yaml
new file mode 100644
index 0000000..5e80e77
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/doctrine.yaml
@@ -0,0 +1,18 @@
+doctrine:
+ dbal:
+ url: '%env(resolve:DATABASE_URL)%'
+
+ # IMPORTANT: You MUST configure your server version,
+ # either here or in the DATABASE_URL env var (see .env file)
+ #server_version: '5.7'
+ orm:
+ auto_generate_proxy_classes: true
+ naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
+ auto_mapping: true
+ mappings:
+ App:
+ is_bundle: false
+ type: annotation
+ dir: '%kernel.project_dir%/src/Entity'
+ prefix: 'App\Entity'
+ alias: App
diff --git a/console/skel/symfony-app/config/packages/doctrine_migrations.yaml b/console/skel/symfony-app/config/packages/doctrine_migrations.yaml
new file mode 100644
index 0000000..3bf0fbc
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/doctrine_migrations.yaml
@@ -0,0 +1,5 @@
+doctrine_migrations:
+ dir_name: '%kernel.project_dir%/src/Migrations'
+ # namespace is arbitrary but should be different from App\Migrations
+ # as migrations classes should NOT be autoloaded
+ namespace: DoctrineMigrations
diff --git a/console/skel/symfony-app/config/packages/mailer.yaml b/console/skel/symfony-app/config/packages/mailer.yaml
new file mode 100644
index 0000000..56a650d
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/mailer.yaml
@@ -0,0 +1,3 @@
+framework:
+ mailer:
+ dsn: '%env(MAILER_DSN)%'
diff --git a/console/skel/symfony-app/config/packages/notifier.yaml b/console/skel/symfony-app/config/packages/notifier.yaml
new file mode 100644
index 0000000..3984a48
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/notifier.yaml
@@ -0,0 +1,16 @@
+framework:
+ notifier:
+ #chatter_transports:
+ # slack: '%env(SLACK_DSN)%'
+ # telegram: '%env(TELEGRAM_DSN)%'
+ #texter_transports:
+ # twilio: '%env(TWILIO_DSN)%'
+ # nexmo: '%env(NEXMO_DSN)%'
+ channel_policy:
+ # use chat/slack, chat/telegram, sms/twilio or sms/nexmo
+ urgent: ['email']
+ high: ['email']
+ medium: ['email']
+ low: ['email']
+ admin_recipients:
+ - { email: admin@example.com }
diff --git a/console/skel/symfony-app/config/packages/prod/doctrine.yaml b/console/skel/symfony-app/config/packages/prod/doctrine.yaml
new file mode 100644
index 0000000..084f59a
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/prod/doctrine.yaml
@@ -0,0 +1,20 @@
+doctrine:
+ orm:
+ auto_generate_proxy_classes: false
+ metadata_cache_driver:
+ type: pool
+ pool: doctrine.system_cache_pool
+ query_cache_driver:
+ type: pool
+ pool: doctrine.system_cache_pool
+ result_cache_driver:
+ type: pool
+ pool: doctrine.result_cache_pool
+
+framework:
+ cache:
+ pools:
+ doctrine.result_cache_pool:
+ adapter: cache.app
+ doctrine.system_cache_pool:
+ adapter: cache.system
diff --git a/console/skel/symfony-app/config/packages/prod/monolog.yaml b/console/skel/symfony-app/config/packages/prod/monolog.yaml
new file mode 100644
index 0000000..14b42bb
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/prod/monolog.yaml
@@ -0,0 +1,24 @@
+monolog:
+ handlers:
+ main:
+ type: fingers_crossed
+ action_level: error
+ handler: nested
+ excluded_http_codes: [404, 405]
+ buffer_size: 50 # How many messages should be saved? Prevent memory leaks
+ nested:
+ type: stream
+ path: "%kernel.logs_dir%/%kernel.environment%.log"
+ level: debug
+ console:
+ type: console
+ process_psr_3_messages: false
+ channels: ["!event", "!doctrine"]
+ deprecation:
+ type: stream
+ path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log"
+ deprecation_filter:
+ type: filter
+ handler: deprecation
+ max_level: info
+ channels: ["php"]
diff --git a/console/skel/symfony-app/config/packages/prod/routing.yaml b/console/skel/symfony-app/config/packages/prod/routing.yaml
new file mode 100644
index 0000000..b3e6a0a
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/prod/routing.yaml
@@ -0,0 +1,3 @@
+framework:
+ router:
+ strict_requirements: null
diff --git a/console/skel/symfony-app/config/packages/routing.yaml b/console/skel/symfony-app/config/packages/routing.yaml
new file mode 100644
index 0000000..7e97762
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/routing.yaml
@@ -0,0 +1,3 @@
+framework:
+ router:
+ utf8: true
diff --git a/console/skel/symfony-app/config/packages/security.yaml b/console/skel/symfony-app/config/packages/security.yaml
new file mode 100644
index 0000000..6c617f2
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/security.yaml
@@ -0,0 +1,51 @@
+security:
+ # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
+ providers:
+ # the name of your user provider can be anything
+ session_user_provider:
+ id: App\Security\AuthUserProvider
+# secured_area:
+# id: session_auth.user_provider
+ firewalls:
+
+ dev:
+ stateless: true
+ access_denied_handler: App\Security\AccessDeniedHandler
+ guard:
+ authenticators:
+ - App\Security\SessionAuthenticator
+ main:
+ stateless: true
+ access_denied_handler: App\Security\AccessDeniedHandler
+ guard:
+ authenticators:
+ - App\Security\SessionAuthenticator
+ # activate different ways to authenticate
+ # https://symfony.com/doc/current/security.html#firewalls-authentication
+
+ # https://symfony.com/doc/current/security/impersonating_user.html
+ # switch_user: true
+
+# secured_area:
+# guard:
+# authenticators:
+# - session_auth.authenticator
+# logout:
+# path: logout #nom de la route de déconnexion
+# target: /
+# success_handler: session_auth.authenticator
+
+ encoders:
+ # use your user class name here
+ App\Security\AuthUser:
+ # Use native password encoder
+ # This value auto-selects the best possible hashing algorithm
+ # (i.e. Sodium when available).
+ algorithm: auto
+ # Easy way to control access for large sections of your site
+ # Note: Only the *first* access control that matches will be used
+ access_control:
+ - { path: '^/admin-test/admin', roles: ROLE_ADMIN }
+ - { path: '^/admin-test/unauthorized', roles: ROLE_USER }
+ - { path: '^/admin-test/page1', roles: ROLE_USER_CONNECTED }
+ - { path: '^/admin-test', roles: ROLE_USER }
diff --git a/console/skel/symfony-app/config/packages/session_auth.yaml b/console/skel/symfony-app/config/packages/session_auth.yaml
new file mode 100755
index 0000000..4e8e740
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/session_auth.yaml
@@ -0,0 +1,42 @@
+#session_auth:
+# #Activation du user_provider interne
+# #par défaut TRUE
+# use_default_provider : false
+# #Le Provider doit implémenter Symfony\Component\Security\Core\User\UserProviderInterface
+# #par défaut est utilise Besancon\AuthBundle\Security\User\AuthUserProvider
+# #cette valeur n'est nécessaire que lorsque use_default_provider est a false
+# #provider: Besancon\AuthBundle\Security\User\AuthUserProvider
+# provider: App\Security\AuthUserProvider
+# #Namespace de l'entité utilisateur
+# #L'entité doit implémenter Symfony\Component\Security\Core\User\UserInterface
+# #par défaut est utilise Besancon\AuthBundle\Security\User\AuthUser
+# #user_entity: App\Besancon\AuthBundle\Security\User\AuthUser
+# user_entity: App\Classes\AuthUser
+# #nom de la route correspondant à la page d'accueil de l'application
+# #par défaut est à NULL
+# homepage: "index"
+# #tag du service personnalisé permettant de gérer l'authentification
+# #par défaut est à bes_auth.authentification (service par défaut)
+# authentication_service: App\Security\Authentification
+# #authentication_service: bes_auth.authentification
+# #Mode d'authentification Cas ou Rsa
+# #obligatoire pas de valeur par défaut
+# type_auth: '%type_auth%'
+# #Configuration pour le mode Cas
+# #obligatoire si mode Cas choisi
+# cas:
+# #Serveur Cas
+# hostname: "seshat25.ac-besancon.fr"
+# #Port Cas
+# port: 443
+# #Uri Cas
+# uri: ""
+# #Configuration pour le mode Rsa
+# #obligatoire si mode Rsa choisi
+# rsa :
+# #Url de déconnexion Rsa
+# logout_url: http://webphppreprod3.in.ac-besancon.fr:8383/login/ct_logout.jsp
+#
+#parameters:
+# type_auth: Session
+
diff --git a/console/skel/symfony-app/config/packages/test/monolog.yaml b/console/skel/symfony-app/config/packages/test/monolog.yaml
new file mode 100644
index 0000000..fc40641
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/test/monolog.yaml
@@ -0,0 +1,12 @@
+monolog:
+ handlers:
+ main:
+ type: fingers_crossed
+ action_level: error
+ handler: nested
+ excluded_http_codes: [404, 405]
+ channels: ["!event"]
+ nested:
+ type: stream
+ path: "%kernel.logs_dir%/%kernel.environment%.log"
+ level: debug
diff --git a/console/skel/symfony-app/config/packages/test/twig.yaml b/console/skel/symfony-app/config/packages/test/twig.yaml
new file mode 100644
index 0000000..8c6e0b4
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/test/twig.yaml
@@ -0,0 +1,2 @@
+twig:
+ strict_variables: true
diff --git a/console/skel/symfony-app/config/packages/test/validator.yaml b/console/skel/symfony-app/config/packages/test/validator.yaml
new file mode 100644
index 0000000..1e5ab78
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/test/validator.yaml
@@ -0,0 +1,3 @@
+framework:
+ validation:
+ not_compromised_password: false
diff --git a/console/skel/symfony-app/config/packages/test/web_profiler.yaml b/console/skel/symfony-app/config/packages/test/web_profiler.yaml
new file mode 100644
index 0000000..03752de
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/test/web_profiler.yaml
@@ -0,0 +1,6 @@
+web_profiler:
+ toolbar: false
+ intercept_redirects: false
+
+framework:
+ profiler: { collect: false }
diff --git a/console/skel/symfony-app/config/packages/translation.yaml b/console/skel/symfony-app/config/packages/translation.yaml
new file mode 100644
index 0000000..05a2b3d
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/translation.yaml
@@ -0,0 +1,6 @@
+framework:
+ default_locale: en
+ translator:
+ default_path: '%kernel.project_dir%/translations'
+ fallbacks:
+ - en
diff --git a/console/skel/symfony-app/config/packages/twig.yaml b/console/skel/symfony-app/config/packages/twig.yaml
new file mode 100644
index 0000000..b3cdf30
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/twig.yaml
@@ -0,0 +1,2 @@
+twig:
+ default_path: '%kernel.project_dir%/templates'
diff --git a/console/skel/symfony-app/config/packages/validator.yaml b/console/skel/symfony-app/config/packages/validator.yaml
new file mode 100644
index 0000000..350786a
--- /dev/null
+++ b/console/skel/symfony-app/config/packages/validator.yaml
@@ -0,0 +1,8 @@
+framework:
+ validation:
+ email_validation_mode: html5
+
+ # Enables validator auto-mapping support.
+ # For instance, basic validation constraints will be inferred from Doctrine's metadata.
+ #auto_mapping:
+ # App\Entity\: []
diff --git a/console/skel/symfony-app/config/routes/annotations.yaml b/console/skel/symfony-app/config/routes/annotations.yaml
new file mode 100644
index 0000000..e92efc5
--- /dev/null
+++ b/console/skel/symfony-app/config/routes/annotations.yaml
@@ -0,0 +1,7 @@
+controllers:
+ resource: ../../src/Controller/
+ type: annotation
+
+kernel:
+ resource: ../../src/Kernel.php
+ type: annotation
diff --git a/console/skel/symfony-app/config/routes/dev/framework.yaml b/console/skel/symfony-app/config/routes/dev/framework.yaml
new file mode 100644
index 0000000..bcbbf13
--- /dev/null
+++ b/console/skel/symfony-app/config/routes/dev/framework.yaml
@@ -0,0 +1,3 @@
+_errors:
+ resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
+ prefix: /_error
diff --git a/console/skel/symfony-app/config/routes/dev/web_profiler.yaml b/console/skel/symfony-app/config/routes/dev/web_profiler.yaml
new file mode 100644
index 0000000..c82beff
--- /dev/null
+++ b/console/skel/symfony-app/config/routes/dev/web_profiler.yaml
@@ -0,0 +1,7 @@
+web_profiler_wdt:
+ resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
+ prefix: /_wdt
+
+web_profiler_profiler:
+ resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
+ prefix: /_profiler
diff --git a/console/skel/symfony-app/config/services.yaml b/console/skel/symfony-app/config/services.yaml
new file mode 100644
index 0000000..b42c417
--- /dev/null
+++ b/console/skel/symfony-app/config/services.yaml
@@ -0,0 +1,43 @@
+# This file is the entry point to configure your own services.
+# Files in the packages/ subdirectory configure your dependencies.
+
+# Put parameters here that don't need to change on each machine where the app is deployed
+# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
+parameters:
+
+services:
+ # default configuration for services in *this* file
+ _defaults:
+ autowire: true # Automatically injects dependencies in your services.
+ autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
+
+ # makes classes in src/ available to be used as services
+ # this creates a service per class whose id is the fully-qualified class name
+ App\:
+ resource: '../src/*'
+ exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
+
+ # controllers are imported separately to make sure services can be injected
+ # as action arguments even if you don't extend any base controller class
+ App\Controller\:
+ resource: '../src/Controller'
+ tags: ['controller.service_arguments']
+
+ # add more service definitions when explicit configuration is needed
+ # please note that last definitions always *replace* previous ones
+# App\Security\Authentification:
+# autowire: true
+# parent: App\Security\Abstracts\AuthFinal
+# public: false
+# autoconfigure: false
+# arguments:
+# $var: []
+#
+# session_auth.authenticator_factory:
+# class: App\Security\AuthentificatorFactory
+# public: false
+#
+# session_auth.user_provider:
+# class: App\Security\AuthUserProvider
+# public: false
+
diff --git a/console/skel/symfony-app/src/Controller/AdminController.php b/console/skel/symfony-app/src/Controller/AdminController.php
new file mode 100755
index 0000000..0059a6a
--- /dev/null
+++ b/console/skel/symfony-app/src/Controller/AdminController.php
@@ -0,0 +1,19 @@
+render('default/page.html.twig', [
+ 'text' => 'admin',
+ ]);
+ }
+}
diff --git a/console/skel/symfony-app/src/Controller/DefaultController.php b/console/skel/symfony-app/src/Controller/DefaultController.php
new file mode 100755
index 0000000..e9757ac
--- /dev/null
+++ b/console/skel/symfony-app/src/Controller/DefaultController.php
@@ -0,0 +1,45 @@
+");
+ //print_r($this->get('session'));
+ print_r($_COOKIE);
+ print_r($_SESSION);
+ print_r("");
+ // replace this example code with whatever you need
+ return $this->render('default/page.html.twig', [
+ 'text' => 'homepage',
+ ]);
+ }
+
+ /**
+ * @Route("/admin-test/page1", name="page1")
+ */
+ public function page1Action()
+ {
+ // replace this example code with whatever you need
+ return $this->render('default/page.html.twig', [
+ 'text' => 'page1',
+ ]);
+ }
+ /**
+ * @Route("/admin-test/page2", name="page2")
+ */
+ public function page2Action()
+ {
+ // replace this example code with whatever you need
+ return $this->render('default/page.html.twig', [
+ 'text' => 'page2',
+ ]);
+ }
+}
diff --git a/console/skel/symfony-app/src/Controller/ErrorController.php b/console/skel/symfony-app/src/Controller/ErrorController.php
new file mode 100755
index 0000000..f5dff9a
--- /dev/null
+++ b/console/skel/symfony-app/src/Controller/ErrorController.php
@@ -0,0 +1,24 @@
+");
+ //print_r($this->get('session'));
+ print_r($_COOKIE);
+ print_r($_SESSION);
+ print_r("");
+ // replace this example code with whatever you need
+ return $this->render('default/unauthorized.html.twig', [
+
+ ]);
+ }
+}
diff --git a/console/skel/symfony-app/src/Events/OnAuthenticationFailureEvent.php b/console/skel/symfony-app/src/Events/OnAuthenticationFailureEvent.php
new file mode 100644
index 0000000..fafda8d
--- /dev/null
+++ b/console/skel/symfony-app/src/Events/OnAuthenticationFailureEvent.php
@@ -0,0 +1,38 @@
+request = $request;
+ $this->exception = $exception;
+ $this->response = new Response($exception->getMessage(), Response::HTTP_FORBIDDEN);
+ }
+
+ public function getRequest() {
+ return $this->request;
+ }
+
+ public function getException() {
+ return $this->exception;
+ }
+
+ public function getResponse() {
+ return $this->response;
+ }
+
+ public function setResponse($response) {
+ $this->response = $response;
+ return $this;
+ }
+
+}
diff --git a/console/skel/symfony-app/src/Events/OnAuthenticationSuccessEvent.php b/console/skel/symfony-app/src/Events/OnAuthenticationSuccessEvent.php
new file mode 100644
index 0000000..e83d65b
--- /dev/null
+++ b/console/skel/symfony-app/src/Events/OnAuthenticationSuccessEvent.php
@@ -0,0 +1,31 @@
+request = $request;
+ $this->token = $token;
+ $this->providerKey = $providerKey;
+ }
+
+ public function getRequest() {
+ return $this->request;
+ }
+
+ public function getToken() {
+ return $this->exception;
+ }
+
+ public function getProviderKey() {
+ return $this->providerKey;
+ }
+
+}
diff --git a/console/skel/symfony-app/src/Kernel.php b/console/skel/symfony-app/src/Kernel.php
new file mode 100644
index 0000000..1cd0572
--- /dev/null
+++ b/console/skel/symfony-app/src/Kernel.php
@@ -0,0 +1,54 @@
+getProjectDir().'/config/bundles.php';
+ foreach ($contents as $class => $envs) {
+ if ($envs[$this->environment] ?? $envs['all'] ?? false) {
+ yield new $class();
+ }
+ }
+ }
+
+ public function getProjectDir(): string
+ {
+ return \dirname(__DIR__);
+ }
+
+ protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
+ {
+ $container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php'));
+ $container->setParameter('container.dumper.inline_class_loader', \PHP_VERSION_ID < 70400 || $this->debug);
+ $container->setParameter('container.dumper.inline_factories', true);
+ $confDir = $this->getProjectDir().'/config';
+
+ $loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob');
+ $loader->load($confDir.'/{packages}/'.$this->environment.'/*'.self::CONFIG_EXTS, 'glob');
+ $loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob');
+ $loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob');
+ }
+
+ protected function configureRoutes(RouteCollectionBuilder $routes): void
+ {
+ $confDir = $this->getProjectDir().'/config';
+
+ $routes->import($confDir.'/{routes}/'.$this->environment.'/*'.self::CONFIG_EXTS, '/', 'glob');
+ $routes->import($confDir.'/{routes}/*'.self::CONFIG_EXTS, '/', 'glob');
+ $routes->import($confDir.'/{routes}'.self::CONFIG_EXTS, '/', 'glob');
+ }
+}
diff --git a/console/skel/symfony-app/src/Repository/.gitkeep b/console/skel/symfony-app/src/Repository/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/console/skel/symfony-app/src/Security/AccessDeniedHandler.php b/console/skel/symfony-app/src/Security/AccessDeniedHandler.php
new file mode 100644
index 0000000..9ee021f
--- /dev/null
+++ b/console/skel/symfony-app/src/Security/AccessDeniedHandler.php
@@ -0,0 +1,26 @@
+twig = $twig;
+ }
+ public function handle(Request $request, AccessDeniedException $accessDeniedException)
+ {
+ $content = $this->twig->render(
+ 'default/unauthorized.html.twig', array()
+ );
+ $response = new Response($content, Response::HTTP_FORBIDDEN);
+ return $response;
+ }
+}
\ No newline at end of file
diff --git a/console/skel/symfony-app/src/Security/AuthUser.php b/console/skel/symfony-app/src/Security/AuthUser.php
new file mode 100644
index 0000000..38bfa47
--- /dev/null
+++ b/console/skel/symfony-app/src/Security/AuthUser.php
@@ -0,0 +1,97 @@
+username = $username;
+ $this->id = $id;
+ $this->credentials = $credentials;
+ $this->roles = $roles;
+ $this->salt = sha1(microtime(true));
+ }
+
+ public function getRoles()
+ {
+ $roles = $this->roles;
+ // guarantee every user at least has ROLE_USER
+ if($this->getId() == 1587184) {
+ $roles[] = 'ROLE_ADMIN';
+ }
+ return array_unique($roles);
+ }
+
+ public function setRoles($roles)
+ {
+ return $this->roles = $roles;
+ }
+
+ public function getUsername()
+ {
+ return $this->username;
+ }
+
+ public function getUser(){
+ return $this;
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function getCredentials()
+ {
+ return $this->credentials;
+ }
+
+ public function eraseCredentials()
+ {
+ // TODO: Implement eraseCredentials() method.
+ }
+
+ public function getPassword()
+ {
+ // TODO: Implement getPassword() method.
+ }
+
+ public function getSalt()
+ {
+ return $this->salt;
+ }
+
+ public function isEqualTo(UserInterface $user)
+ {
+ if (!$user instanceof AuthUser) {
+ return false;
+ }
+
+ if ($this->username !== $user->getUsername()) {
+ return false;
+ }
+ if ($this->id !== $user->getId()) {
+ return false;
+ }
+ if ($this->type !== $user->getType()) {
+ return false;
+ }
+ if ($this->status !== $user->getStatus()) {
+ return false;
+ }
+
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/console/skel/symfony-app/src/Security/AuthUserProvider.php b/console/skel/symfony-app/src/Security/AuthUserProvider.php
new file mode 100644
index 0000000..5e8c42a
--- /dev/null
+++ b/console/skel/symfony-app/src/Security/AuthUserProvider.php
@@ -0,0 +1,77 @@
+entity_user;
+
+ return $this->authService->getUser($username);
+ // Load a User object from your data source or throw UsernameNotFoundException.
+ // The $username argument may not actually be a username:
+ // it is whatever value is being returned by the getUsername()
+ // method in your User class.
+// throw new \Exception('TODO: fill in loadUserByUsername() inside '.__FILE__);
+ }
+
+ /**
+ * Refreshes the user after being reloaded from the session.
+ *
+ * When a user is logged in, at the beginning of each request, the
+ * User object is loaded from the session and then this method is
+ * called. Your job is to make sure the user's data is still fresh by,
+ * for example, re-querying for fresh User data.
+ *
+ * If your firewall is "stateless: true" (for a pure API), this
+ * method is not called.
+ *
+ * @return UserInterface
+ */
+
+ public function refreshUser(UserInterface $user) {
+ $user = $this->_ctrlInstanceUser($user);
+
+ return $this->loadUserByUsername($user->getUsername());
+ }
+
+ private function _ctrlInstanceUser(UserInterface $user) {
+ $entity_user = $this->entity_user;
+
+ if (!$user instanceof $entity_user) {
+ throw new UnsupportedUserException(
+ sprintf('Instances of "%s" are not supported.', get_class($user))
+ );
+ }
+
+ return $user;
+ }
+
+ /**
+ * Tells Symfony to use this provider for this User class.
+ */
+ public function supportsClass($class)
+ {
+ return AuthUser::class === $class;
+ }
+}
\ No newline at end of file
diff --git a/console/skel/symfony-app/src/Security/SessionAuthenticator.php b/console/skel/symfony-app/src/Security/SessionAuthenticator.php
new file mode 100644
index 0000000..7d149c8
--- /dev/null
+++ b/console/skel/symfony-app/src/Security/SessionAuthenticator.php
@@ -0,0 +1,152 @@
+router = $router;
+ $this->twig = $twig;
+ }
+
+ /**
+ * Called on every request to decide if this authenticator should be
+ * used for the request. Returning `false` will cause this authenticator
+ * to be skipped.
+ */
+ public function supports(Request $request)
+ {
+ Dumper::dump("supports function");
+ Dumper::dump($_SESSION);
+ if (isset($_SESSION['id_utilisateur'])) {
+ return true;
+ }else{
+ return true;
+ }
+ }
+
+ /**
+ * Called on every request. Return whatever credentials you want to
+ * be passed to getUser() as $credentials.
+ */
+ public function getCredentials(Request $request)
+ {
+ Dumper::dump("getCredentials");
+ return "X-AUTH-TOKEN-SESSION-API";
+ }
+
+ public function getUser($credentials, UserProviderInterface $userProvider)
+ {
+ Dumper::dump("getUser");
+ if (!isset($_SESSION['id'])) {
+ $user = new \App\Security\AuthUser('0','not-connected',$credentials,['ROLE_USER']);
+ }else {
+ $user = new \App\Security\AuthUser($_SESSION['id'], $_SESSION['username'], $credentials, ['ROLE_USER', 'ROLE_USER_CONNECTED']);
+ }
+
+ Dumper::dump($user);
+
+ // if a User is returned, checkCredentials() is called
+ return $user;
+ }
+
+ public function checkCredentials($credentials, UserInterface $user)
+ {
+ // Check credentials - e.g. make sure the password is valid.
+ // In case of an API token, no credential check is needed.
+
+ // Return `true` to cause authentication success
+ Dumper::dump("checkCredentials");
+ if($user->getCredentials() === $credentials) {
+ return true;
+ }else{
+ return false;
+ }
+ }
+
+ public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
+ {
+ // on success, let the request continue
+ //return null;
+ }
+
+ public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
+ {
+ $data = [
+ // you may want to customize or obfuscate the message first
+ 'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
+
+ // or to translate this message
+ // $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
+ ];
+
+// return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
+// $url = $this->router->generate('unauthorized');
+// return new RedirectResponse($url);
+ $content = $this->twig->render(
+ 'default/unauthorized.html.twig', array()
+ );
+ $response = new Response($content, Response::HTTP_FORBIDDEN);
+ return $response;
+
+
+ }
+
+ /**
+ * Called when authentication is needed, but it's not sent
+ */
+ public function start(Request $request, AuthenticationException $authException = null)
+ {
+ $data = [
+ // you might translate this message
+ 'message' => 'Authentication Required'
+ ];
+
+ //return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
+
+// $url = $this->router->generate('unauthorized');
+// return new RedirectResponse($url);
+
+ $content = $this->twig->render(
+ 'default/unauthorized.html.twig', array()
+ );
+ $response = new Response($content, Response::HTTP_FORBIDDEN);
+ return $response;
+
+
+
+ }
+
+ public function supportsRememberMe()
+ {
+ return false;
+ }
+
+ public function onLogoutSuccess(Request $request) {
+ //$homepage = $this->config["homepage"];
+ //return \phpCAS::logoutWithRedirectService($this->urlGenerator->generate($homepage, array(), UrlGeneratorInterface::ABSOLUTE_URL));
+ header('Location: /index.php');
+ return ;
+ }
+
+}
\ No newline at end of file
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/DependencyInjection/Configuration.php b/console/skel/symfony-app/src/Session/AuthBundle/DependencyInjection/Configuration.php
new file mode 100755
index 0000000..0b9119a
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/DependencyInjection/Configuration.php
@@ -0,0 +1,110 @@
+getRootNode();
+ $rootNode
+ ->children()
+ ->scalarNode('homepage')->defaultNull()->end()
+ ->scalarNode('authentication_service')->defaultNull()->end()
+ ->scalarNode('provider')->defaultNull()->end()
+ ->booleanNode('use_default_provider')->defaultTrue()->end()
+ ->scalarNode('user_entity')->defaultNull()->end()
+ ->scalarNode('type_auth')->isRequired()->cannotBeEmpty()
+ ->validate()
+ ->ifNotInArray(array('Rsa', 'Cas','Session'))
+ ->thenInvalid("La méthode d'authentification %s n'est pas gérée, seuls Rsa et Cas sont acceptés")
+ ->end()
+ ->end()
+ ->scalarNode('environment')->end()
+ ->end()
+ ;
+
+ $rootNode
+ ->validate()
+ ->ifTrue(function ($v) {
+ if(!is_null($v['user_entity'])){
+ $class = $v['user_entity'];
+ if(!class_exists($class)){
+ return true;
+ }
+ return !array_key_exists("Symfony\Component\Security\Core\User\UserInterface", class_implements($class));
+ }
+ return false;
+ })
+ ->thenInvalid("La classe renseignée pour 'entity' doit implémenter Symfony\Component\Security\Core\User\UserInterface")
+ ->end();
+
+ $this->_addCasConfig($rootNode);
+ $this->_addRsaConfig($rootNode);
+
+ return $treeBuilder;
+ }
+
+ private function _addCasConfig(ArrayNodeDefinition $node) {
+
+ $node
+ ->children()
+ ->arrayNode('cas')->info('A déclarer si authentification pas CAS.')
+ ->addDefaultsIfNotSet()
+ ->treatNullLike(['hostname' => null])
+ ->treatNullLike(['port' => null])
+ ->treatNullLike(['uri' => null])
+ ->children()
+ ->scalarNode('hostname')->defaultNull()->end()
+ ->scalarNode('port')->defaultNull()->end()
+ ->scalarNode('uri')->defaultNull()->end()
+ ->end()
+ ->end()
+ ->end()
+ ;
+
+ $node
+ ->validate()
+ ->ifTrue(function ($v) {
+ $cas_config = $v['cas'];
+ return ($v['type_auth']=="Cas" && (is_null($cas_config['hostname']) || is_null($cas_config['port']) || is_null($cas_config['uri'])) );
+ })
+ ->thenInvalid("En utilisant le type d'authentification Cas vous devez renseigner la section 'cas' et ses clés 'hostname', 'port', 'uri'")
+ ->end();
+ }
+
+ private function _addRsaConfig(ArrayNodeDefinition $node) {
+ $node
+ ->children()
+ ->arrayNode('rsa')->addDefaultsIfNotSet()->info('A déclarer si authentification pas RSA.')
+ ->addDefaultsIfNotSet()
+ ->treatNullLike(['logout_url' => null])
+ ->children()
+ ->scalarNode('logout_url')->defaultNull()->end()
+ ->end()
+ ->end()
+ ->end()
+ ;
+
+ $node
+ ->validate()
+ ->ifTrue(function ($v) {
+ $rsa_config = $v['rsa'];
+ return ($v['type_auth']==="Rsa" && is_null($rsa_config['logout_url']));
+ })
+ ->thenInvalid("En utilisant le type d'authentification Rsa vous devez renseigner la section 'rsa' et sa clé 'logout_url'")
+ ->end();
+ }
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/DependencyInjection/SessionAuthExtension.php b/console/skel/symfony-app/src/Session/AuthBundle/DependencyInjection/SessionAuthExtension.php
new file mode 100755
index 0000000..c6c4d72
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/DependencyInjection/SessionAuthExtension.php
@@ -0,0 +1,82 @@
+getParameter("kernel.environment");
+ $configuration = new Configuration();
+ $config = $this->processConfiguration($configuration, $configs);
+ $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
+ //Chargement des parametres
+ $loader->load('parameters.yml');
+ //Chargement des services
+ $loader->load('services.yml');
+
+
+ //definition du service d'authentification par défaut dans le cas où ce ne serait pas un service
+ // fraichement créé par l'utilisateur dans le fichiers services.yaml
+ if(is_null($config["authentication_service"])){
+ $authentication_service = "session_auth.authentification";
+ }else{
+ $authentication_service = $config["authentication_service"];
+ }
+
+ if ($authentication_service == "session_auth.authentification") {
+ $container->register($authentication_service, \App\Besancon\AuthBundle\Security\DefaultAuthentication::class)
+ ->addMethodCall('setGetterAttributes', array($config))
+ ->setPublic(false);
+ }
+
+ //Creation du service @bes_auth.authenticator permettant la redirection sur le Cas ou le Rsa correspondant
+ $container->register('session_auth.authenticator', \Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator::class)
+ ->setFactory(array(new Reference("session_auth.authenticator_factory"), 'getAuthenticator'))
+ ->addArgument(new Reference($authentication_service))
+ ->addArgument($config)
+ ->addArgument(new Reference("router"))
+ ->addArgument(new Reference("event_dispatcher"))
+ ->setPublic(false);
+
+
+ //Création du service pour le provider par défaut ou pour le provider défini par l'utilisateur
+ if ($config["use_default_provider"]) {
+ //Creation du service @bes_auth.user_provider
+ $container->register('session_auth.user_provider', \App\Besancon\AuthBundle\Security\User\AuthUserProvider::class)
+ ->addArgument(new Reference($authentication_service))
+ ->addArgument($config)
+ ->setPublic(false);
+ }else{
+ $container->register('session_auth.user_provider', $config["provider"])
+ ->addArgument(new Reference($authentication_service))
+ ->addArgument($config)
+ ->setPublic(false);
+ }
+
+ $container->setDefinition('session_auth.configuration', new \Symfony\Component\DependencyInjection\Definition( \App\Besancon\AuthBundle\DependencyInjection\Configuration::class) )
+ ->setArguments([
+ $config,
+ ]);
+ }
+
+ public function getNamespace() {
+ return 'http://ac-besancon.fr/schema/dic/' . $this->getAlias();
+ }
+
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Events/CheckCredentialsEvent.php b/console/skel/symfony-app/src/Session/AuthBundle/Events/CheckCredentialsEvent.php
new file mode 100755
index 0000000..5e753bb
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Events/CheckCredentialsEvent.php
@@ -0,0 +1,34 @@
+credentials = $credentials;
+ $this->user_interface = $user_interface;
+ }
+
+ public function getCredentials() {
+ return $this->credentials;
+ }
+
+ public function getUserInterface() {
+ return $this->user_interface;
+ }
+
+ public function getAccess() {
+ return $this->access;
+ }
+ public function setAccess($access) {
+ $this->access = $access;
+ return $this;
+ }
+
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Events/OnAuthenticationFailureEvent.php b/console/skel/symfony-app/src/Session/AuthBundle/Events/OnAuthenticationFailureEvent.php
new file mode 100755
index 0000000..a3c1d75
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Events/OnAuthenticationFailureEvent.php
@@ -0,0 +1,38 @@
+request = $request;
+ $this->exception = $exception;
+ $this->response = new Response($exception->getMessage(), Response::HTTP_FORBIDDEN);
+ }
+
+ public function getRequest() {
+ return $this->request;
+ }
+
+ public function getException() {
+ return $this->exception;
+ }
+
+ public function getResponse() {
+ return $this->response;
+ }
+
+ public function setResponse($response) {
+ $this->response = $response;
+ return $this;
+ }
+
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Events/OnAuthenticationSuccessEvent.php b/console/skel/symfony-app/src/Session/AuthBundle/Events/OnAuthenticationSuccessEvent.php
new file mode 100755
index 0000000..ea3c4a8
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Events/OnAuthenticationSuccessEvent.php
@@ -0,0 +1,31 @@
+request = $request;
+ $this->token = $token;
+ $this->providerKey = $providerKey;
+ }
+
+ public function getRequest() {
+ return $this->request;
+ }
+
+ public function getToken() {
+ return $this->exception;
+ }
+
+ public function getProviderKey() {
+ return $this->providerKey;
+ }
+
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/README.md b/console/skel/symfony-app/src/Session/AuthBundle/README.md
new file mode 100755
index 0000000..d18dd1a
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/README.md
@@ -0,0 +1,366 @@
+**AuthBundle**
+========================
+
+# Configuration minimale requise
+
+Le bundle est compatible à partir de la version 3.4 de Symfony.
+
+# Installation
+
+## Installation via composer (recommandé)
+
+Dans un premier temps renseigner le "repository" via la commande :
+
+```bash
+composer config repositories.authbundle git "ssh://git@gitlab1.in.ac-besancon.fr:1232/abelhadjali/authbundle.git"
+```
+
+Ceci va ajouter dans votre fichier composer.json les lignes suivantes
+
+```json
+ ...
+ "repositories": {
+ "authbundle": {
+ "type": "git",
+ "url": "ssh://git@gitlab1.in.ac-besancon.fr:1232/abelhadjali/authbundle.git"
+ }
+ }
+ ...
+```
+
+Puis ajouter la dépendance au bundle en précisant le tag de la version souhaitée ici à partir de la v0.1
+
+```bash
+composer require ac-besancon/authbundle:^0.1
+```
+
+Enfin activer le bundle en suivant les instructions de la section [[AuthBundle#Activation du bundle|Activation du bundle]]
+
+## Installation sans composer
+
+### Récupérer les sources
+
+*Copier et coller* le dossier Besancon du Bundle dans le repertoire _*src/*_ de votre projet *Symfony*.
+
+### Déclaration du namespace
+
+Dans le fichier `composer.json` et dans la section "autoload" de votre projet ajouter:
+
+```json
+ "autoload": {
+ "psr-4": {
+ ...
+ "Besancon\\AuthBundle\\": "src/Besancon/AuthBundle",
+ ...
+ }
+```
+
+Puis executer la commande composer suivante :
+
+```bash
+composer dump-autoload
+```
+
+
+# Activation du bundle
+
+Pour activer le Bundle, ouvrir le fichier app/AppKernel.php et y ajouter:
+
+```php
+
+// ...
+class AppKernel extends Kernel
+{
+ public function registerBundles()
+ {
+ $bundles = array(
+ // ...
+ new Besancon\AuthBundle\BesanconAuthBundle(),
+ );
+
+ // ...
+ }
+
+ // ...
+}
+```
+
+
+# Configuration
+======================
+
+## Liste complète des options de configuration
+
+La configuration est à déclaré dans le fichier *app/config/config.yml* du projet Symfony.
+
+```yaml
+besancon_auth:
+ #Activation du user_provider interne
+ #par défaut TRUE
+ use_default_provider : true
+ #Namespace de l'entité utilisateur
+ #L'entité doit implémenter Symfony\Component\Security\Core\User\UserInterface
+ #par défaut est utilise Besancon\AuthBundle\Security\User\AuthUser
+ user_entity: Mon\Entite\User
+ #nom de la route correspondant à la page d'accueil de l'application
+ #par défaut est à NULL
+ homepage: "homepage"
+ #tag du service personnalisé permettant de gérer l'authentification
+ #par défaut est à bes_auth.authentification (service par défaut)
+ authentication_service: mon_service.authentification
+ #Mode d'authentification Cas ou Rsa
+ #obligatoire pas de valeur par défaut
+ type_auth: Cas
+ #Configuration pour le mode Cas
+ #obligatoire si mode Cas choisi
+ cas:
+ #Serveur Cas
+ hostname: "seshat23.ac-besancon.fr"
+ #Port Cas
+ port: 8443
+ #Uri Cas
+ uri: ""
+ #Configuration pour le mode Rsa
+ #obligatoire si mode Rsa choisi
+ rsa :
+ #Url de déconnexion Rsa
+ logout_url: http://url.deconnexion.fr/login/ct_logout.jsp
+```
+
+## Configuration dans le firewall
+
+Ouvrir le fichier app/config/security.yml du projet Symfony.
+
+Si utilisation du _user provider_ interne *bes_auth.user_provider* , alors le déclarer dans la section _*providers*_ :
+
+```yaml
+...
+providers:
+ app:
+ id: bes_auth.user_provider
+...
+```
+
+Sinon préciser votre propre user provider
+
+Toujours dans le même fichier, dans la section des _*firewalls*_, déclarer le _guard_ *bes_auth.authenticator* dans la zone à sécurisée :
+
+```yaml
+ firewalls:
+ ...
+ secured_area:
+ logout_on_user_change: true
+ ...
+ guard:
+ authenticators:
+ - bes_auth.authenticator
+ logout:
+ path: auth_cas_logout #nom de la route de déconnexion
+ target: /
+ success_handler: bes_auth.authenticator
+ ...
+```
+
+Plus d'infos sur le user provider :
+* https://symfony.com/doc/current/security/entity_provider.html#using-a-custom-query-to-load-the-user
+
+Il est donc important de définir la route de déconnexion dans le fichier *app/config/route.yml*
+
+```yaml
+...
+
+auth_cas_logout:
+ path: /logout
+
+...
+```
+
+## Configuration avancée
+
+### Création d'un service d'authentification
+
+Pour cela, créer un service qui hérite de *AuthAbstract* et implémente *AuthInterface*
+
+```php
+
+twig->render(
+ * '@App/Test/forbiden.html.twig', array()
+ * );
+ * $response = new Response($content, Response::HTTP_FORBIDDEN);
+ * return $response;
+ * }
+ * ```
+ *
+ * @param AuthenticationException $exception
+ * Exception générée par le provider
+ *
+ * @return Symfony\Component\HttpFoundation\Response
+ *
+ */
+ public function onAuthenticationFailure(\Symfony\Component\Security\Core\Exception\AuthenticationException $exception);
+
+```
+
+Enfin lorsque le service est prêt, le déclarer, en le reliant à la classe parent Besancon\AuthBundle\Security\Abstracts\AuthAbstract:
+
+```yaml
+
+ mon_service.authentification:
+ class: AppBundle\Security\Auth\MonService
+ parent: Besancon\AuthBundle\Security\Abstracts\AuthAbstract
+ public: false
+
+#OU si version Symfony >=3.4
+
+ AppBundle\Security\Auth\MonService:
+ autowire: true
+ parent: Besancon\AuthBundle\Security\Abstracts\AuthAbstract
+ public: false
+ autoconfigure: false
+
+```
+
+Puis déclarer dans la configuration ([[AuthBundle#Liste complète des options de configuration|Liste complète des options de configuration]]) du bundle le nom du service personnalisé :
+
+```yaml
+besancon_auth:
+...
+ authentication_service: mon_service.authentification
+
+#OU si version Symfony >=3.4
+
+ authentication_service: AppBundle\Security\Auth\MonService
+
+ ...
+```
+
+# Personnaliser la page en cas d'échec d'authentification
+
+En cas d'échec lors de l'authentification (exemple ctrlAccess() retourne false) , par défaut, le bundle renvoie une page blanche avec le message renvoyé par l'exception qui a généré l'erreur.
+Afin de personnaliser cette page, il faut passer par la création d'un service comme indiqué dans le paragraphe [[AuthBundle#Création d'un service d'authentification|Création d'un service d'authentification]] et de redéfinir la méthode *onAuthenticationFailure*.
+
+Voici un exemple :
+
+```php
+
+class MonService extends AuthAbstract implements AuthInterface
+{
+
+
+ public function __construct(Twig_Environment $twig)
+ {
+ $this->twig = $twig;
+ }
+
+ ...
+
+ public function onAuthenticationFailure(\Symfony\Component\Security\Core\Exception\AuthenticationException $exception)
+ {
+
+ $content = $this->twig->render(
+ '@App/Test/forbiden.html.twig', array()
+ );
+ $response = new Response($content, Response::HTTP_FORBIDDEN);
+ return $response;
+ }
+}
+```
+
+Nous pouvons remarquer que dans cet exemple, le service prend en paramètre dans le constructeur $twig qui est l'instance de Twig de notre applciation.
+Pour que cela fonctionne, il faut auparavant avoir passer le tag twig à notre service :
+
+```php
+ ...
+ AppBundle\Security\Auth\MonService:
+ autowire: true
+ parent: Besancon\AuthBundle\Security\Abstracts\AuthAbstract
+ public: false
+ autoconfigure: false
+ arguments: ['@twig']
+ ...
+```
+
+Ainsi lorsqu'une personne tentera de se connecter et qu'il n'aura, par exemple, pas les droits nécessaires le template @App/Test/forbiden.html.twig sera chargé.
\ No newline at end of file
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Resources/config/parameters.yml b/console/skel/symfony-app/src/Session/AuthBundle/Resources/config/parameters.yml
new file mode 100755
index 0000000..951efcd
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Resources/config/parameters.yml
@@ -0,0 +1,40 @@
+parameters:
+ #auth_cas devra s'appeler auth_multi
+ #bes_auth.authentication_service: bes_auth.authentification
+ session_auth:
+ type_auth: Session
+ environment: "%kernel.environment%"
+ cas:
+ #defini l'entité correspondant aux utilisateurs pour la création automatique des comptes
+ server:
+ cas_hostname: "seshat23.ac-besancon.fr"
+ cas_port: 8443
+ cas_uri: ""
+ route:
+ after_connect: "homepage"
+ rsa :
+ logout_url: http://webphppreprod.in.ac-besancon.fr/login/ct_logout.jsp
+ login_url: ~
+ route:
+ after_connect: "homepage"
+ #Gérer les droits d'accès à l'application en fonction des attributs CAS
+ access:
+# allow:
+# attributes :
+# - ["[phpCAS][attributes][title]","[phpCAS][attributes][ABservice]"]
+# - "[phpCAS][attributes][FrEduRne]"
+# values : ["DIR|^DSS","^.*\\$TEC\\$"]
+ #deny:
+ #attributes : "title"
+ #values : "ENS"
+ #@TODO : Association profile CAS et Role de l'appli
+# profil:
+# cas:
+# ROLE_ADMIN:
+# key: "[phpCAS][attributes][typensi]"
+# value: "A"
+# ROLE_USER:
+# key: "[phpCAS][attributes][FrEduRne]"
+# value: "^0250069P"
+# control: "regex"
+
\ No newline at end of file
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Resources/config/parameters.yml.dist b/console/skel/symfony-app/src/Session/AuthBundle/Resources/config/parameters.yml.dist
new file mode 100755
index 0000000..609bffa
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Resources/config/parameters.yml.dist
@@ -0,0 +1,38 @@
+parameters:
+ #auth_cas devra s'appeler auth_multi
+ session_auth:
+ environment: "%kernel.environment%"
+ #defini l'entité correspondant aux utilisateurs pour la création automatique des comptes
+ server:
+ cas_hostname: "seshat23.ac-besancon.fr"
+ cas_port: 8443
+ cas_uri: ""
+ route:
+ after_connect: "homepage"
+ #Gérer les droits d'accès à l'application en fonction des attributs CAS
+ access:
+# allow:
+# attributes :
+# - ["[phpCAS][attributes][title]","[phpCAS][attributes][ABservice]"]
+# - "[phpCAS][attributes][FrEduRne]"
+# values : ["DIR|^DSS","^.*\\$TEC\\$"]
+ deny:
+ attributes : "title"
+ values : "ENS"
+ #@TODO : Association profile CAS et Role de l'appli
+# profil:
+# cas:
+# ROLE_ADMIN:
+# key: "[phpCAS][attributes][typensi]"
+# value: "A"
+# ROLE_USER:
+# key: "[phpCAS][attributes][FrEduRne]"
+# value: "^0250069P"
+# control: "regex"
+ auth_rsa :
+ environment: "%kernel.environment%"
+ login_url: http://webphppreprod.in.ac-besancon.fr/login/ct_logon_mixte.jsp
+ logout_url: http://webphppreprod.in.ac-besancon.fr/login/ct_logout.jsp
+ route:
+ after_connect: "homepage"
+
\ No newline at end of file
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Resources/config/routing.yml b/console/skel/symfony-app/src/Session/AuthBundle/Resources/config/routing.yml
new file mode 100755
index 0000000..107ea3e
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Resources/config/routing.yml
@@ -0,0 +1,3 @@
+#besancon_auth_homepage:
+# path: /
+# defaults: { _controller: BesanconAuthBundle:Default:index }
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Resources/config/services.yml b/console/skel/symfony-app/src/Session/AuthBundle/Resources/config/services.yml
new file mode 100755
index 0000000..4b0bb3e
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Resources/config/services.yml
@@ -0,0 +1,11 @@
+services:
+
+ session_auth.authenticator_factory:
+ class: App\Session\AuthBundle\Security\AuthenticatorFactory
+ public: false
+
+ #bes_auth.authentification:
+ # class: App\Besancon\AuthBundle\Security\Auth\Authentication
+ # parent: App\Besancon\AuthBundle\Security\Abstracts\AuthFinal
+ # public: false
+ # autoconfigure: false
\ No newline at end of file
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Resources/doc/index.md b/console/skel/symfony-app/src/Session/AuthBundle/Resources/doc/index.md
new file mode 100755
index 0000000..f6dd483
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Resources/doc/index.md
@@ -0,0 +1,66 @@
+Installation
+============
+
+1: Installation
+---------------------------
+
+Copier et coller le dossier Besancon du Bundle dans src/
+
+
+2: Activer le Bundle
+-------------------------
+
+Pour activer le Bundle, ouvrir le fichier `app/AppKernel.php` et y ajouter:
+
+```php
+
+ *
+ * @method setGetterAttributes()
+ * @method getUser()
+ * @abstract
+ */
+
+namespace App\Session\AuthBundle\Security\Abstracts;
+
+use App\Session\AuthBundle\Utils\Config;
+use Symfony\Component\HttpFoundation\Response;
+
+abstract class AuthAbstract {
+
+ /**
+ * @var App\Besancon\AuthBundle\Security\Interfaces\AttributesInterface $ai Instance de CasAttributes ou RsaAttributes
+ */
+ protected $ai;
+
+ /**
+ * Intancie le getters en fonction de la configuration
+ *
+ * Si dans la config le paramètre type_auth est défini à CAS alors
+ * intanciation du getter CasAttributes,
+ * Si la valeur est à RSA alors instanciation du getter RsaAttributes
+ *
+ * Cette instance peut ensuite être utilisée dans le service d'authentification
+ * qui héritera de AuthAbstract, en passant faisant appel à $this->ai
+ *
+ * @final
+ * @param $config
+ * configuration du Bundle
+ * @return void
+ *
+ * */
+ abstract public function setGetterAttributes($config);
+
+ /**
+ * Comportement par défaut lorsque l'authentification n'aboutie pas (accès non autorisé)
+ *
+ * il est possible de redéfinir cette méthode
+ * mais elle doit renvoyer une réponse HTTP exemple:
+ * - Symfony\Component\HttpFoundation\Response
+ * - Symfony\Component\HttpFoundation\JsonResponse
+ *
+ * @param \Symfony\Component\Security\Core\Exception\AuthenticationException $exception
+ * Exception généré par le guard
+ * @return Symfony\Component\HttpFoundation\Response
+ *
+ * */
+ abstract public function onAuthenticationFailure(\Symfony\Component\Security\Core\Exception\AuthenticationException $exception);
+
+ /**
+ * Renvoie une instance de l'utilisateur
+ *
+ * Ceci correspond à la class Besancon\AuthBundle\Security\User\AuthUser,
+ * il est possible de redéfinir cette méthode
+ * mais elle doit renvoyer un objet implementant Symfony\Component\Security\Core\User\UserInterface
+ *
+ * Est utilisé dans le userprovider par défaut Besancon\AuthBundle\Security\User\AuthUserProvider
+ *
+ * @see \Symfony\Component\Security\Core\User\UserInterface
+ * @see \Besancon\AuthBundle\Security\User\AuthUserProvider
+ *
+ * @param string $username
+ * Identifiant de l'utilisateur
+ * @return \Symfony\Component\Security\Core\User\UserInterface
+ *
+ */
+ abstract public function getUser($username);
+
+
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Security/Abstracts/AuthFinal.php b/console/skel/symfony-app/src/Session/AuthBundle/Security/Abstracts/AuthFinal.php
new file mode 100755
index 0000000..cd9e4e8
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Security/Abstracts/AuthFinal.php
@@ -0,0 +1,87 @@
+
+ *
+ * @method setGetterAttributes()
+ * @method getUser()
+ * @abstract
+ */
+
+namespace App\Session\AuthBundle\Security\Abstracts;
+
+use App\Session\AuthBundle\Utils\Config;
+use Symfony\Component\HttpFoundation\Response;
+
+class AuthFinal extends AuthAbstract {
+
+ /**
+ * Intancie le getters en fonction de la configuration
+ *
+ * Si dans la config le paramètre type_auth est défini à CAS alors
+ * intanciation du getter CasAttributes,
+ * Si la valeur est à RSA alors instanciation du getter RsaAttributes
+ *
+ * Cette instance peut ensuite être utilisée dans le service d'authentification
+ * qui héritera de AuthAbstract, en passant faisant appel à $this->ai
+ *
+ * @final
+ * @param $config
+ * configuration du Bundle
+ * @return void
+ *
+ * */
+ public function setGetterAttributes($config) {
+ $type_auth = Config::getDeclaredType($config);
+ //dump('calls');
+ $getters = "\App\Session\AuthBundle\Security\Getters\\" . $type_auth . "Attributes";
+ $ai = new $getters();
+ $this->ai = $ai;
+ //dump($this->ai);
+ }
+
+ /**
+ * Comportement par défaut lorsque l'authentification n'aboutie pas (accès non autorisé)
+ *
+ * il est possible de redéfinir cette méthode
+ * mais elle doit renvoyer une réponse HTTP exemple:
+ * - Symfony\Component\HttpFoundation\Response
+ * - Symfony\Component\HttpFoundation\JsonResponse
+ *
+ * @param \Symfony\Component\Security\Core\Exception\AuthenticationException $exception
+ * Exception généré par le guard
+ * @return Symfony\Component\HttpFoundation\Response
+ *
+ * */
+ public function onAuthenticationFailure(\Symfony\Component\Security\Core\Exception\AuthenticationException $exception) {
+ return new Response($exception->getMessage(), Response::HTTP_FORBIDDEN);
+ }
+
+ /**
+ * Renvoie une instance de l'utilisateur
+ *
+ * Ceci correspond à la class Besancon\AuthBundle\Security\User\AuthUser,
+ * il est possible de redéfinir cette méthode
+ * mais elle doit renvoyer un objet implementant Symfony\Component\Security\Core\User\UserInterface
+ *
+ * Est utilisé dans le userprovider par défaut Besancon\AuthBundle\Security\User\AuthUserProvider
+ *
+ * @see \Symfony\Component\Security\Core\User\UserInterface
+ * @see \Besancon\AuthBundle\Security\User\AuthUserProvider
+ *
+ * @param string $username
+ * Identifiant de l'utilisateur
+ * @return \Symfony\Component\Security\Core\User\UserInterface
+ *
+ */
+ public function getUser($username) {
+ $roles_service = $this->getRoles();
+ $roles = (!is_null($roles_service) && is_array($roles_service)) ? $roles_service : array();
+ $user = new \App\Besancon\AuthBundle\Security\User\AuthUser($username, md5("8sQaz87dPPsdanYakq86f" . $username), $roles);
+
+ return $user;
+ }
+
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Security/Abstracts/GetterAbstract.php b/console/skel/symfony-app/src/Session/AuthBundle/Security/Abstracts/GetterAbstract.php
new file mode 100755
index 0000000..4f69232
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Security/Abstracts/GetterAbstract.php
@@ -0,0 +1,42 @@
+
+ */
+
+namespace App\Session\AuthBundle\Security\Abstracts;
+
+/**
+ * Description of GetterAbstract
+ *
+ * @author belhadjali
+ */
+abstract class GetterAbstract {
+
+ public function isACP(){
+ return $this->getFrEduFonctAdm() == "ACP";
+ }
+
+ public function isDIR(){
+ return $this->getFrEduFonctAdm() == "DIR";
+ }
+
+ public function isDEC(){
+ return $this->getFrEduFonctAdm() == "DEC";
+ }
+
+ public function isDIR1D(){
+ return $this->isDEC();
+ }
+
+ public function isIEN1D (){
+ return $this->getFrEduFonctAdm() == "IEN1D";
+ }
+
+ public function isDIO(){
+ return $this->getFrEduFonctAdm() == "IEN1D";
+ }
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Security/AuthenticatorFactory.php b/console/skel/symfony-app/src/Session/AuthBundle/Security/AuthenticatorFactory.php
new file mode 100755
index 0000000..696dced
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Security/AuthenticatorFactory.php
@@ -0,0 +1,27 @@
+urlGenerator = $urlGenerator;
+ //Récupérer le service déaclaré authService
+ $this->authService = $authService;
+ $this->config = $config;
+ $this->dispatcher = $dispatcher;
+
+ if (php_sapi_name() !== 'cli') {
+ \phpCAS::client(CAS_VERSION_2_0, $this->config['cas']["hostname"], $this->config['cas']["port"], $this->config['cas']["uri"]);
+ \phpCAS::setNoCasServerValidation();
+ \phpCAS::forceAuthentication();
+ }
+ }
+
+ /**
+ * Called on every request. Return whatever credentials you want,
+ * or null to stop authentication.
+ */
+ public function getCredentials(Request $request) {
+ return true;
+ }
+
+ public function getUser($credentials, UserProviderInterface $userProvider) {
+ $username = \phpCAS::getUser();
+ $user = $userProvider->loadUserByUsername($username);
+ return $user;
+ }
+
+ public function checkCredentials($credentials, UserInterface $user) {
+ return $this->authService->ctrlAccess($user);
+ }
+
+ public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) {
+
+ $event = new OnAuthenticationSuccessEvent($request, $token, $providerKey);
+ $this->dispatcher->dispatch(OnAuthenticationSuccessEvent::NAME, $event);
+
+ $this->authService->onSuccess($token);
+// on success, let the request continue
+ }
+
+ public function onAuthenticationFailure(Request $request, AuthenticationException $exception) {
+
+ $event = new OnAuthenticationFailureEvent($request, $exception);
+ $this->dispatcher->dispatch(OnAuthenticationFailureEvent::NAME, $event);
+
+ return $this->authService->onAuthenticationFailure($exception);
+ }
+
+ /**
+ * Called when authentication is needed, but it's not sent
+ */
+// public function start(Request $request, AuthenticationException $authException = null) {
+// $url = $this->router->generate('login');
+// return new RedirectResponse($url);
+// }
+
+ public function supportsRememberMe() {
+ return false;
+ }
+
+//implementation LogoutSuccessHandlerInterface
+ public function onLogoutSuccess(Request $request) {
+ $homepage = $this->config["homepage"];
+ return \phpCAS::logoutWithRedirectService($this->urlGenerator->generate($homepage, array(), UrlGeneratorInterface::ABSOLUTE_URL));
+ }
+
+ protected function getLoginUrl() {
+ return \phpCas::getServerLoginURL();
+ }
+
+ public function supports(Request $request) {
+ if (isset($this->config['environment']) && $this->config['environment'] == "test") {
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Security/DefaultAuthentication.php b/console/skel/symfony-app/src/Session/AuthBundle/Security/DefaultAuthentication.php
new file mode 100755
index 0000000..1b58b51
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Security/DefaultAuthentication.php
@@ -0,0 +1,106 @@
+ai->getUsername();
+ $password = "";
+
+ $unauthenticatedToken = new UsernamePasswordToken(
+ $username,
+ $password,
+ 'secured_area'
+ );
+
+ $userProvider = new UserProvider( new Authentication(),
+ array('user_entity' => 'App\Session\AuthBundle\Security\Auth\User',
+ 'type_auth' => 'Cas'));
+ $userChecker = new UserChecker();
+
+ $defaultEncoder = new MessageDigestPasswordEncoder('sha512', true, 5000);
+
+ $encoders = [
+ User::class => $defaultEncoder,
+ ];
+
+ $encoderFactory = new EncoderFactory($encoders);
+
+ $provider = new DaoAuthenticationProvider(
+ $userProvider,
+ $userChecker,
+ 'secured_area',
+ $encoderFactory);
+
+
+ $authenticatedToken = $provider
+ ->authenticate($unauthenticatedToken);
+
+ //$tokenStorage = new TokenStorage();
+
+ //$tokenStorage->setToken($authenticatedToken);
+ }
+
+ public function getRoles() {
+ return [];
+ }
+
+ public function onSuccess($token) {
+
+ //dump($this->ai);
+ //die('success');
+
+ //$this->authentificate($token);
+
+ $token->setAttribute("username", $this->ai->getUsername());
+ $token->setAttribute("complet_name", $this->ai->getCompletName());
+ $token->setAttribute("mail", $this->ai->getMail());
+ $token->setAttribute("FreDuRne", $this->ai->getFreDuRne());
+
+ return;
+ }
+
+ public function ctrlAccess(\Symfony\Component\Security\Core\User\UserInterface $user) {
+ //die('ctrlAccess');
+ return true;
+ }
+
+ public function getUser($username) {
+ return parent::getUser($username);
+ }
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Security/Getters/CasAttributes.php b/console/skel/symfony-app/src/Session/AuthBundle/Security/Getters/CasAttributes.php
new file mode 100755
index 0000000..dd4e564
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Security/Getters/CasAttributes.php
@@ -0,0 +1,76 @@
+
+ */
+
+namespace App\Session\AuthBundle\Security\Getters;
+
+use App\Session\AuthBundle\Security\Interfaces\AttributesInterface;
+
+/**
+ * Class CasAttributes
+ *
+ * Cette classe permet d'accèder aux informations (attributs) de l'utilisateur
+ * renvoyé par CAS à partir des méthodes d'accès définies dans l'interface AttributesInterface
+ *
+ */
+class CasAttributes implements AttributesInterface {
+
+ public function getFirstName() {
+ return \phpCAS::getAttribute("prenom");
+ }
+
+ public function getCompletName() {
+ return \phpCAS::getAttribute("nomcomplet");
+ }
+
+ public function getName() {
+ return \phpCAS::getAttribute("nom");
+ }
+
+ public function getDiscipline() {
+ return \phpCAS::getAttribute("discipline");
+ }
+
+ public function getFonctM() {
+ return \phpCAS::getAttribute("fonctm");
+ }
+
+ public function getRne() {
+ return \phpCAS::getAttribute("rne");
+ }
+
+ public function getFreDuRne() {
+ return \phpCAS::getAttribute("FrEduRne");
+ }
+
+ public function getFreDuRneResp() {
+ return \phpCAS::getAttribute("FrEduRneResp");
+ }
+
+ public function getMail() {
+ return \phpCAS::getAttribute("mail");
+ }
+
+ public function getTitle() {
+ return \phpCAS::getAttribute("title");
+ }
+
+ public function getUsername() {
+ return \phpCAS::getUser();
+ }
+
+ public function getFrEduResDel(){
+ return \phpCAS::getAttribute("FrEduResDel");
+ }
+
+ public function getFrEduFonctAdm() {
+ return \phpCAS::getAttribute("FrEduFonctAdm");
+ }
+
+ public function getGrade() {
+ return \phpCAS::getAttribute("grade");
+ }
+
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Security/Getters/RsaAttributes.php b/console/skel/symfony-app/src/Session/AuthBundle/Security/Getters/RsaAttributes.php
new file mode 100755
index 0000000..d00688a
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Security/Getters/RsaAttributes.php
@@ -0,0 +1,80 @@
+
+ *
+ */
+
+namespace App\Besancon\AuthBundle\Security\Getters;
+
+use App\Besancon\AuthBundle\Security\Interfaces\AttributesInterface;
+
+/**
+ * Class RsaAttributes
+ *
+ * Cette classe permet d'accèder aux informations (entête HTTP) de l'utilisateur
+ * renvoyé par RSA CT à partir des méthodes d'accès définies dans l'interface AttributesInterface
+ *
+ */
+class RsaAttributes implements AttributesInterface {
+
+ public function getCompletName() {
+ return (isset($_SERVER['HTTP_CN'])) ? $_SERVER['HTTP_CN'] : null;
+ }
+
+ public function getDiscipline() {
+ return (isset($_SERVER['HTTP_DISCIPLINE'])) ? $_SERVER['HTTP_DISCIPLINE'] : null;
+ }
+
+ public function getFonctM() {
+ return (isset($_SERVER['HTTP_FONCTM'])) ? $_SERVER['HTTP_FONCTM'] : null;
+ }
+
+ public function getRne() {
+ return (isset($_SERVER['HTTP_RNE'])) ? $_SERVER['HTTP_FREDURNE'] : null;
+ }
+
+ public function getFreDuRne() {
+ return (isset($_SERVER['HTTP_FREDURNE'])) ? explode(',', $_SERVER['HTTP_FREDURNE']) : null;
+ }
+
+ public function getFreDuRneResp() {
+ return (isset($_SERVER['HTTP_FREDURNERESP'])) ? explode(',', $_SERVER['HTTP_FREDURNERESP']) : null;
+ }
+
+ public function getMail() {
+ return (isset($_SERVER['HTTP_CTEMAIL'])) ? $_SERVER['HTTP_CTEMAIL'] : null;
+ }
+
+ public function getTitle() {
+ return (isset($_SERVER['HTTP_TITLE'])) ? $_SERVER['HTTP_TITLE'] : null;
+ }
+
+ public function getUsername() {
+ return (isset($_SERVER['HTTP_CT_REMOTE_USER'])) ? $_SERVER['HTTP_CT_REMOTE_USER'] : null;
+ }
+
+ public function getFrEduResDel() {
+ return (isset($_SERVER['HTTP_FREDURESDEL'])) ? $_SERVER['HTTP_FREDURESDEL'] : null;
+ }
+
+ public function getFrEduFonctAdm() {
+ return (isset($_SERVER['HTTP_FREDUFONCTADM'])) ? $_SERVER['HTTP_FREDUFONCTADM'] : null;
+ }
+
+ public function getFirstName() {
+ return (isset($_SERVER['HTTP_CTFN'])) ? $_SERVER['HTTP_CTFN'] : null;
+ }
+
+ public function getName() {
+ return (isset($_SERVER['HTTP_CTLN'])) ? $_SERVER['HTTP_CTLN'] : null;
+ }
+
+ public function getGrade() {
+ return (isset($_SERVER['HTTP_GRADE'])) ? $_SERVER['HTTP_GRADE'] : null;
+ }
+
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Security/Getters/SessionAttributes.php b/console/skel/symfony-app/src/Session/AuthBundle/Security/Getters/SessionAttributes.php
new file mode 100644
index 0000000..457c813
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Security/Getters/SessionAttributes.php
@@ -0,0 +1,76 @@
+
+ */
+
+namespace App\Session\AuthBundle\Security\Getters;
+
+use App\Session\AuthBundle\Security\Interfaces\AttributesInterface;
+
+/**
+ * Class CasAttributes
+ *
+ * Cette classe permet d'accèder aux informations (attributs) de l'utilisateur
+ * renvoyé par CAS à partir des méthodes d'accès définies dans l'interface AttributesInterface
+ *
+ */
+class SessionAttributes implements AttributesInterface {
+
+ public function getFirstName() {
+ return ;
+ }
+
+ public function getCompletName() {
+ return ;
+ }
+
+ public function getName() {
+ return ;
+ }
+
+ public function getDiscipline() {
+ return ;
+ }
+
+ public function getFonctM() {
+ return ;
+ }
+
+ public function getRne() {
+ return ;
+ }
+
+ public function getFreDuRne() {
+ return ;
+ }
+
+ public function getFreDuRneResp() {
+ return ;
+ }
+
+ public function getMail() {
+ return ;
+ }
+
+ public function getTitle() {
+ return ;
+ }
+
+ public function getUsername() {
+ return ;
+ }
+
+ public function getFrEduResDel(){
+ return ;
+ }
+
+ public function getFrEduFonctAdm() {
+ return ;
+ }
+
+ public function getGrade() {
+ return ;
+ }
+
+}
\ No newline at end of file
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Security/Interfaces/AttributesInterface.php b/console/skel/symfony-app/src/Session/AuthBundle/Security/Interfaces/AttributesInterface.php
new file mode 100755
index 0000000..35367d0
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Security/Interfaces/AttributesInterface.php
@@ -0,0 +1,215 @@
+
+ *
+ */
+
+namespace App\Session\AuthBundle\Security\Interfaces;
+
+/**
+ * Interface AttributesInterface
+ *
+ */
+interface AttributesInterface
+{
+
+ const NO_VALUE = "X";
+
+ const FREDURNE_OFFSET_RNE = 0;
+ const FREDURNE_OFFSET_SECTEUR = 2;
+ const FREDURNE_OFFSET_FONCTION_EXERCICE = 3;
+ const FREDURNE_OFFSET_FONCTION_RNEUAJ = 4;
+ const FREDURNE_OFFSET_1CODETNA = 5; // 1er chiffre code nature nomenclature
+ const FREDURNE_OFFSET_CODETTY = 6; // code type etablissement nomenclature
+ const FREDURNE_OFFSET_CODETNA = 7; // code nature etablissement nomenclature
+
+
+
+ const FREDURNERESP_OFFSET_RNE = 0;
+ const FREDURNERESP_OFFSET_SECTEUR = 2; //PU ou PR
+ const FREDURNERESP_OFFSET_AFFECTATION = 3; // A pour Affectation anticipé N pour affectation normale F pour affectation qui fini le 31/08
+ const FREDURNERESP_OFFSET_1CODETNA = 4; // 1er chiffre code nature nomenclature
+ const FREDURNERESP_OFFSET_CODETTY = 5; // code type etablissement nomenclature
+ const FREDURNERESP_OFFSET_CODETNA = 6; // code nature nomenclature
+
+
+ const TYPE_LYCEE_GENERAL = "LYC";
+ const TYPE_LYCEE_PRO = "LP";
+ const TYPE_COLLEGE = "CLG";
+ const TYPE_SEGPA = "SES";
+
+ const CODE_NATURE_RECTORAT = ["802"];
+ const CODE_NATURE_DSDEN = ["806"];
+ const CODE_NATURE_INSPECTION = ["809"];
+ const CODE_NATURE_LYCEE_GENERAL_ET_TECHNO = ["300"];
+ const CODE_NATURE_LYCEE_TECHNO = ["301"];
+ const CODE_NATURE_LYCEE_GENERAL = ["302", "306"];
+ const CODE_NATURE_LYCEE_AGRICOLE = ["307"];
+ const CODE_NATURE_LYCEE_PRO = ["320"];
+ const CODE_NATURE_COLLEGE = ["340"];
+ const CODE_COLLEGE_NATURE_SPE = ["352"];
+ const CODE_NATURE_SEGPA = ["390"];
+
+
+ const GRADES_IEN = ["1152", "1151"];
+
+ const GRADES_RECTEUR = ["0201"];
+ const GRADES_SG = ["0211", "0911", "0912"];
+ const GRADES_ASG = ["0981"];
+
+ const GRADES_DASEN = ["0921", "0922"];
+ const GRADES_ADJOINT_DASEN = ["0971"];
+
+ const CODES_DISCIPLINE_ASH = ["N0006"];
+ const CODES_DISCIPLINE_DIR = ["D0010"];
+ const CODES_DISCIPLINE_ADJOINT_DIR = ["D0011"];
+ /**
+ * Renvoie le prénom de l'agent
+ *
+ * Correspond au champ "givenName" du LDAP
+ *
+ * @return string|null
+ * prénom de l'agent
+ */
+ public function getFirstName();
+
+ /**
+ * Renvoie l'identifiant LDAP de l'agent
+ *
+ * Correspond au champ "uid" du LDAP
+ *
+ * @return string|null
+ * uid de l'agent
+ */
+ public function getUsername();
+
+ /**
+ * Renvoie le nom de famille de l'agent
+ *
+ * Correspond au champ "sn" du LDAP
+ *
+ * @return string|null
+ * nom de l'agent
+ */
+ public function getName();
+
+ /**
+ * Renvoie l'adresse mail de l'agent
+ *
+ * Correspond au champ "mail" du LDAP
+ *
+ * @return string|null
+ * adresse mail de l'agent
+ */
+ public function getMail();
+
+ /**
+ * Renvoie le nom complet de l'agent
+ *
+ * Correspond au champ "cn" du LDAP
+ *
+ * @return string|null
+ * nom complete de l'agent
+ */
+ public function getCompletName();
+
+ /**
+ * Renvoie le title de l'agent
+ *
+ * Correspond au champ "title" du LDAP
+ *
+ * @return string|null
+ * title de l'agent
+ */
+ public function getTitle();
+
+ /**
+ * Renvoie le code discipline de l'agent
+ *
+ * Correspond au champ "discipline" du LDAP
+ *
+ * @return string|null
+ * code discipline de l'agent
+ */
+ public function getDiscipline();
+
+ /**
+ * Renvoie l'établissements d'affectation de l'agent
+ *
+ * Correspond au champ "rne" du LDAP
+ *
+ * @return string|null
+ * * établissement d'affectation de l'agent
+ */
+ public function getRne();
+
+ /**
+ * Renvoie l'établissements d’exercice de l'agent
+ *
+ * Correspond au champ "FreDuRne" du LDAP
+ *
+ * @return array|null
+ * établissement(s) d'exercice de l'agent
+ */
+ public function getFreDuRne();
+
+ /**
+ * Renvoie le(s) établissement(s) en responsabilité de l'agent
+ *
+ * Correspond au champ "FreDuRneResp" du LDAP
+ *
+ * @return array|null
+ * établissement(s) en responsabalité de l'agent
+ */
+ public function getFreDuRneResp();
+
+ /**
+ * Renvoie le(s) déléguation(s)/attribution(s) de l'agent ouvrant des droits d'accès
+ * à une ressource d'une application pour un ou des rne
+ *
+ * Correspond au champ "FreDuRneDel" du LDAP
+ *
+ * @return array|null
+ * déléguation(s)/attribution(s) de l'agent
+ */
+ public function getFrEduResDel();
+
+ /**
+ * Renvoie la fonction administrative de l'agent
+ * correspondant à un profil particulier
+ *
+ * Correspond au champ "FrEduFonctAdm" du LDAP
+ *
+ * @return string|null
+ * fonction administrative de l'agent
+ */
+ public function getFrEduFonctAdm();
+
+ /**
+ * Renvoie la fonction de l'agent
+ * Attention : initialisé à la création de la fiche avec la même valeur que l’attribut fonction.
+ * Puis, par l’application Annuaire, l’agent peut le modifier.
+ *
+ * Correspond au champ "fonctm" du LDAP
+ *
+ * @return string|null
+ * fonction de l'agent
+ */
+ public function getFonctM();
+
+ /**
+ * Renvoie le grade de l'agent
+ * Alimenté à partir de la valeur agt.gradco
+ * Se référer à la base des nomenclatures dans la table N_GRADE pour voir
+ * les correspondances : http://infocentre.pleiade.education.fr/bcn/workspace/viewTable/n/N_GRADE
+ *
+ * Correspond au champ "grade" du LDAP
+ *
+ * @return string|null
+ * fonction de l'agent
+ */
+ public function getGrade();
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Security/Interfaces/AuthInterface.php b/console/skel/symfony-app/src/Session/AuthBundle/Security/Interfaces/AuthInterface.php
new file mode 100755
index 0000000..0df0e01
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Security/Interfaces/AuthInterface.php
@@ -0,0 +1,100 @@
+
+ *
+ */
+namespace App\Session\AuthBundle\Security\Interfaces;
+
+
+use Symfony\Component\Security\Core\User\UserInterface;
+
+interface AuthInterface {
+
+ /**
+ * Contrôle de l'accès à partir des attributs CAS ou RSA
+ *
+ * Vérifier les droits d'accès à l'application à partir des attributs récupérées des getters :
+ * - CasAttributes
+ * - RsaAttributes
+ *
+ * @param UserInterface $user
+ * L'entité user récupéré par le provider
+ *
+ * @return bool
+ * - true si accès autorisé
+ * - false si accès refusé
+ */
+ public function ctrlAccess(UserInterface $user);
+
+ /**
+ * Calcule et retoune le(s) rôle(s) à partir des attributs CAS ou RSA
+ *
+ * Calculer le(s) rôle(s) à partir des attributs récupérées des getters :
+ * - CasAttributes
+ * - RsaAttributes
+ * Doit retourner un tableau même vide
+ *
+ * @return array
+ */
+ public function getRoles();
+
+ /**
+ * Retourne un utilisateur pour la génération du token, si l'utilisateur n'existe pas en base de donnée
+ *
+ * ATTENTION : CETTE METHODE DOIT ÊTRE REDEFINIE SI UTILISATION D'UNE ENTITE UTILISTEUR
+ * DIFFERENTE DE CELLE UTILISEE PAR DEFAUT
+ *
+ * @param String $username
+ * uid de l'utilisateur récupéré de Cas ou Rsa
+ *
+ * @return UserInterface
+ */
+ public function getUser($username);
+
+ /**
+ * Traitement personnalisé après récupération du token
+ *
+ * Il est possible d'enrichir le token (attributs...) ou d'effectuer des contrôles supplémentaire
+ *
+ * @param $token
+ * Token d'authification généré
+ *
+ * @return null
+ */
+ public function onSuccess($token);
+
+ /**
+ * Traitement personnalisé lorsque la connexion n'a pas abouti
+ *
+ * Vérifié l'exception généré et adapter l'action (redirection, déconnexion...)
+ *
+ * Doit retourner un objet de type Response
+ *
+ * Exemple :
+ *
+ * ```
+ * public function onAuthenticationFailure(\Symfony\Component\Security\Core\Exception\AuthenticationException $exception)
+ * {
+ * $content = $this->twig->render(
+ * '@App/Test/forbiden.html.twig', array()
+ * );
+ * $response = new Response($content, Response::HTTP_FORBIDDEN);
+ * return $response;
+ * }
+ * ```
+ *
+ * @param AuthenticationException $exception
+ * Exception générée par le provider
+ *
+ * @return Symfony\Component\HttpFoundation\Response
+ *
+ */
+ public function onAuthenticationFailure(\Symfony\Component\Security\Core\Exception\AuthenticationException $exception);
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Security/RsaAuthenticator.php b/console/skel/symfony-app/src/Session/AuthBundle/Security/RsaAuthenticator.php
new file mode 100755
index 0000000..d2d9ed0
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Security/RsaAuthenticator.php
@@ -0,0 +1,120 @@
+urlGenerator = $urlGenerator;
+ //Récupérer le service déaclaré authService
+ $this->authService = $authService;
+ $this->config = $config;
+ $this->dispatcher = $dispatcher;
+ }
+
+ /**
+ * Called on every request. Return whatever credentials you want,
+ * or null to stop authentication.
+ */
+ public function getCredentials(Request $request) {
+ if (!isset($_SERVER['HTTP_CT_REMOTE_USER']) || empty($_SERVER['HTTP_CT_REMOTE_USER'])) {
+ $this->returnRequest = $request->getUri();
+ throw new \LogicException("Impossible de continuer sous RSA : L'entête HTTP_CT_REMOTE_USER est vide ou manquante");
+ }
+ return true;
+ }
+
+ public function getUser($credentials, UserProviderInterface $userProvider) {
+ $username = $_SERVER['HTTP_CT_REMOTE_USER'];
+ $user = $userProvider->loadUserByUsername($username);
+ return $user;
+ }
+
+ public function checkCredentials($credentials, UserInterface $user) {
+ $this->authService->ctrlAccess($user);
+ // check credentials - e.g. make sure the password is valid
+ // no credential check is needed in this case
+ // return true to cause authentication success
+ return true;
+ }
+
+ public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) {
+
+ $event = new OnAuthenticationSuccessEvent($request, $token, $providerKey);
+ $this->dispatcher->dispatch(OnAuthenticationSuccessEvent::NAME, $event);
+
+ $this->authService->onSuccess($token);
+ // on success, let the request continue
+ }
+
+ public function onAuthenticationFailure(Request $request, AuthenticationException $exception) {
+
+ $event = new OnAuthenticationFailureEvent($request, $exception);
+ $this->dispatcher->dispatch(OnAuthenticationFailureEvent::NAME, $event);
+
+ return $this->authService->onAuthenticationFailure($exception);
+ }
+
+ /**
+ * Called when authentication is needed, but it's not sent
+ */
+// public function start(Request $request, AuthenticationException $authException = null) {
+// $url = $this->router->generate('login');
+// return new RedirectResponse($url);
+// }
+
+ public function supportsRememberMe() {
+ return false;
+ }
+
+ //implementation LogoutSuccessHandlerInterface
+ public function onLogoutSuccess(Request $request) {
+ $redirect = (isset($_SERVER['HTTP_FREDUURLRETOUR'])) ? $_SERVER['HTTP_FREDUURLRETOUR'] : $this->config['rsa']['logout_url'];
+ return new RedirectResponse($redirect);
+ }
+
+ protected function getLoginUrl() {
+ $return_request = urlencode($this->returnRequest);
+ $params = "?CT_ORIG_URL=" . $return_request;
+ return $this->config['rsa']['login_url'] . $params;
+ }
+
+ public function supports(Request $request) {
+ if (isset($this->config['environment']) && $this->config['environment'] == "test") {
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Security/SessionAuthenticator.php b/console/skel/symfony-app/src/Session/AuthBundle/Security/SessionAuthenticator.php
new file mode 100644
index 0000000..a081536
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Security/SessionAuthenticator.php
@@ -0,0 +1,127 @@
+router = $router;
+ }
+
+ /**
+ * Called on every request to decide if this authenticator should be
+ * used for the request. Returning `false` will cause this authenticator
+ * to be skipped.
+ */
+ public function supports(Request $request)
+ {
+ if (isset($_SESSION['id_utilisateur'])) {
+ return true;
+ }else{
+ return true;
+ }
+ }
+
+ /**
+ * Called on every request. Return whatever credentials you want to
+ * be passed to getUser() as $credentials.
+ */
+ public function getCredentials(Request $request)
+ {
+ return "X-AUTH-TOKEN-SESSION-API";
+ }
+
+ public function getUser($credentials, UserProviderInterface $userProvider)
+ {
+ if (!isset($_SESSION['id_utilisateur'])) {
+ $user = new \App\Classes\AuthUser('','','','','',['ROLE_USER']);
+ }else {
+ $user = new \App\Classes\AuthUser($_SESSION['id_utilisateur'], $_SESSION['identifiant'], $_SESSION['status_compte'], $_SESSION['type_compte'],$credentials, ['ROLE_USER', 'ROLE_USER_CONNECTED']);
+ }
+
+ // if a User is returned, checkCredentials() is called
+ return $user;
+ }
+
+ public function checkCredentials($credentials, UserInterface $user)
+ {
+ // Check credentials - e.g. make sure the password is valid.
+ // In case of an API token, no credential check is needed.
+
+ // Return `true` to cause authentication success
+ if($user->getCredentials() === $credentials) {
+ return true;
+ }else{
+ return false;
+ }
+ }
+
+ public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
+ {
+ // on success, let the request continue
+ //return null;
+ }
+
+ public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
+ {
+ $data = [
+ // you may want to customize or obfuscate the message first
+ 'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
+
+ // or to translate this message
+ // $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
+ ];
+
+// return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
+ $url = $this->router->generate('unauthorized');
+ return new RedirectResponse($url);
+
+ }
+
+ /**
+ * Called when authentication is needed, but it's not sent
+ */
+ public function start(Request $request, AuthenticationException $authException = null)
+ {
+ $data = [
+ // you might translate this message
+ 'message' => 'Authentication Required'
+ ];
+
+ //return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
+
+ $url = $this->router->generate('unauthorized');
+ return new RedirectResponse($url);
+
+
+ }
+
+ public function supportsRememberMe()
+ {
+ return false;
+ }
+
+ public function onLogoutSuccess(Request $request) {
+ //$homepage = $this->config["homepage"];
+ //return \phpCAS::logoutWithRedirectService($this->urlGenerator->generate($homepage, array(), UrlGeneratorInterface::ABSOLUTE_URL));
+ header('Location: /index.php');
+ return ;
+ }
+
+}
\ No newline at end of file
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Security/Traits/ProfilsCalculator.php b/console/skel/symfony-app/src/Session/AuthBundle/Security/Traits/ProfilsCalculator.php
new file mode 100755
index 0000000..d878644
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Security/Traits/ProfilsCalculator.php
@@ -0,0 +1,332 @@
+ai->getDiscipline(), AttributesInterface::GRADES_RECTEUR);
+ }
+
+ //est secrétaire général d'académie
+ public function isSG()
+ {
+ return in_array($this->ai->getDiscipline(), AttributesInterface::GRADES_SG);
+ }
+
+ //est adjoint au secrétaire général d'académie
+ public function isASG()
+ {
+ return in_array($this->ai->getDiscipline(), AttributesInterface::GRADES_ASG);
+ }
+
+ //agent comptable
+ public function isACP()
+ {
+ return $this->ai->getFrEduFonctAdm() == "ACP";
+ }
+
+ //enseignant
+ public function isENS()
+ {
+ return $this->ai->getFrEduFonctAdm() == AttributesInterface::NO_VALUE && $this->ai->getTitle() == "ENS" && $this->ai->getFrEduRneResp() == AttributesInterface::NO_VALUE;
+ }
+
+ //agent issue d'AGAPE PRIVE
+ public function isAgentPrive()
+ {
+ return $this->ai->getTypensi() == "R";
+ }
+
+ //equipe de direction établissement
+ public function isGroupeDIR()
+ {
+ return $this->ai->getFrEduFonctAdm() == "DIR";
+ }
+
+ //directeur 2nd degré
+ public function isDIR()
+ {
+ return $this->isGroupeDIR() && in_array($this->ai->getDiscipline(), AttributesInterface::CODES_DISCIPLINE_DIR);
+ }
+
+ //directeur adjoint 2nd degré
+ public function isAdjointDIR()
+ {
+ return $this->isGroupeDIR() && in_array($this->ai->getDiscipline(), AttributesInterface::CODES_DISCIPLINE_ADJOINT_DIR);
+ }
+
+ //directeur d'ecole
+ public function isDEC()
+ {
+ return $this->ai->getFrEduFonctAdm() == "DEC";
+ }
+
+ //alias directeur d'ecole
+ public function isDIR1D()
+ {
+ return $this->isDEC();
+ }
+
+ //adaptation scolaire et de la scolarisation des élèves handicapé
+ public function isASH()
+ {
+ return in_array($this->ai->getDiscipline(), AttributesInterface::CODES_DISCIPLINE_ASH);
+ }
+ //est inspecteur
+ public function isIEN()
+ {
+ return (!is_null($this->ai->getGrade())) ? in_array($this->ai->getGrade(), AttributesInterface::GRADES_IEN) : $this->ai->getTitle() == "INS";
+ }
+
+ //est inspecteur 1er degré
+ public function isIEN1D()
+ {
+ return $this->isIEN() && $this->ai->getFrEduFonctAdm() == "IEN1D";
+ }
+
+ //est inspecteur ASH
+ public function isIENASH()
+ {
+ return $this->isASH() && $this->isIEN();
+ }
+
+ //est DASEN
+ public function isDASEN()
+ {
+ return in_array($this->ai->getGrade(), AttributesInterface::GRADES_DASEN);
+ }
+
+ //est adjoint DASEN
+ public function isAdjointDasen()
+ {
+ return in_array($this->ai->getGrade(), AttributesInterface::GRADES_ADJOINT_DASEN);
+ }
+
+ //est directeur CIO
+ public function isDIO()
+ {
+ return $this->ai->getFrEduFonctAdm() == "DIO";
+ }
+
+ public function filterFrEduRneByType($type)
+ {
+ if ($this->ai->getFrEduRne() == AttributesInterface::NO_VALUE) {
+ return [];
+ }
+ $FrEduRne = (!is_array($this->ai->getFrEduRne())) ? [$this->ai->getFrEduRne()] : $this->ai->getFrEduRne();
+
+ $uais = array_filter($FrEduRne, function ($value) use ($type) {
+ $arr_value = explode("$", $value);
+ if (!is_array($arr_value) || !array_key_exists(AttributesInterface::FREDURNE_OFFSET_CODETTY, $arr_value)) {
+ return false;
+ }
+ if (is_array($type)) {
+ return in_array($arr_value[AttributesInterface::FREDURNE_OFFSET_CODETTY], $type);
+ }
+ return $arr_value[AttributesInterface::FREDURNE_OFFSET_CODETTY] == $type;
+ });
+
+ return $uais;
+ }
+
+ public function filterFrEduRneByNature($nature)
+ {
+ if ($this->ai->getFrEduRne() == AttributesInterface::NO_VALUE) {
+ return [];
+ }
+ $FrEduRne = (!is_array($this->ai->getFrEduRne())) ? [$this->ai->getFrEduRne()] : $this->ai->getFrEduRne();
+ $uais = array_filter($FrEduRne, function ($value) use ($nature) {
+ $arr_value = explode("$", $value);
+ if (!is_array($arr_value) || !array_key_exists(AttributesInterface::FREDURNE_OFFSET_CODETNA, $arr_value)) {
+ return false;
+ }
+ if (is_array($nature)) {
+ return in_array($arr_value[AttributesInterface::FREDURNE_OFFSET_CODETNA], $nature);
+ }
+ return $arr_value[AttributesInterface::FREDURNE_OFFSET_CODETNA] == $nature;
+ });
+
+ return $uais;
+ }
+
+ public function filterFrEduRneRespByNature($nature)
+ {
+ if ($this->ai->getFrEduRneResp() == AttributesInterface::NO_VALUE) {
+ return [];
+ }
+ $FrEduRneResp = (!is_array($this->ai->getFrEduRneResp())) ? [$this->ai->getFrEduRneResp()] : $this->ai->getFrEduRneResp();
+
+ $uais = array_filter($FrEduRneResp, function ($value) use ($nature) {
+ $arr_value = explode("$", $value);
+ if (!is_array($arr_value) || !array_key_exists(AttributesInterface::FREDURNERESP_OFFSET_CODETNA, $arr_value)) {
+ return false;
+ }
+ if (is_array($nature)) {
+ return in_array($arr_value[AttributesInterface::FREDURNERESP_OFFSET_CODETNA], $nature);
+ }
+ return $arr_value[AttributesInterface::FREDURNERESP_OFFSET_CODETNA] == $nature;
+ });
+
+ return $uais;
+ }
+
+ public function filterFrEduRneRespByType($type)
+ {
+ if ($this->ai->getFrEduRneResp() == AttributesInterface::NO_VALUE) {
+ return [];
+ }
+ $FrEduRneResp = (!is_array($this->ai->getFrEduRneResp())) ? [$this->ai->getFrEduRneResp()] : $this->ai->getFrEduRneResp();
+
+ $uais = array_filter($FrEduRneResp, function ($value) use ($type) {
+ $arr_value = explode("$", $value);
+ if (!is_array($arr_value) || !array_key_exists(AttributesInterface::FREDURNERESP_OFFSET_CODETTY, $arr_value)) {
+ return false;
+ }
+ if (is_array($type)) {
+ return in_array($arr_value[AttributesInterface::FREDURNERESP_OFFSET_CODETTY], $type);
+ }
+ return $arr_value[AttributesInterface::FREDURNERESP_OFFSET_CODETTY] == $type;
+ });
+
+ return $uais;
+ }
+
+ // public function hasLYC()
+ // {
+ // return $this->findUaiRespByType(AttributesInterface::TYPE_LYCEE_GENERAL);
+ // }
+
+ // public function hasLYCP()
+ // {
+ // return $this->findUaiRespByType(AttributesInterface::TYPE_LYCEE_PRO);
+ // }
+
+
+ public function isAffectedToRectorat()
+ {
+ $result = $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_RECTORAT);
+ return (count($result)) ? true : false;
+ }
+
+ public function isAffectedToDSDEN()
+ {
+ $result = $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_DSDEN);
+ return (count($result)) ? true : false;
+ }
+
+ public function isAffectedToLYC()
+ {
+ $result = $this->filterFrEduRneByType(AttributesInterface::TYPE_LYCEE_GENERAL);
+ return (count($result)) ? true : false;
+ }
+
+ public function isAffectedToLP()
+ {
+ $result = $this->filterFrEduRneByType(AttributesInterface::TYPE_LYCEE_PRO);
+ return (count($result)) ? true : false;
+ }
+
+ public function isAffectedToInspection()
+ {
+ $result = $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_INSPECTION);
+ return (count($result)) ? true : false;
+ }
+
+ public function isAffectedToSEGPA()
+ {
+ $result = $this->filterFrEduRneByType(AttributesInterface::TYPE_SEGPA);
+ return (count($result)) ? true : false;
+ }
+
+ public function isRespOfLYC()
+ {
+ $result = $this->filterFrEduRneRespByType(AttributesInterface::TYPE_LYCEE_GENERAL);
+ return (count($result)) ? true : false;
+ }
+
+ public function isRespOfLP()
+ {
+ $result = $this->filterFrEduRneRespByType(AttributesInterface::TYPE_LYCEE_PRO);
+ return (count($result)) ? true : false;
+ }
+
+ public function isRespOfSEGPA()
+ {
+ $result = $this->filterFrEduRneRespByType(AttributesInterface::TYPE_SEGPA);
+ return (count($result)) ? true : false;
+ }
+
+ /****************************************************************************************
+ * Filtres sur FrEduRne
+ ***************************************************************************************/
+
+ public function filterFrEduRneByLYCG()
+ {
+ return $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_LYCEE_GENERAL);
+ }
+
+ public function filterFrEduRneByLYCGT()
+ {
+ return $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_LYCEE_GENERAL_ET_TECHNO);
+ }
+
+ public function filterFrEduRneByLP()
+ {
+ return $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_LYCEE_GENERAL_ET_TECHNO);
+ }
+
+ public function filterFrEduRneByCLG()
+ {
+ return $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_COLLEGE);
+ }
+
+ public function filterFrEduRneByLYCAG()
+ {
+ return $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_LYCEE_AGRICOLE);
+ }
+
+ public function filterFrEduRneBySEGPA()
+ {
+ return $this->filterFrEduRneByNature(AttributesInterface::CODE_NATURE_SEGPA);
+ }
+
+
+ /****************************************************************************************
+ * Filtres sur FrEduRneResp
+ ***************************************************************************************/
+
+ public function filterFrEduRneRespByLYCG()
+ {
+ return $this->filterFrEduRneRespByNature(AttributesInterface::CODE_NATURE_LYCEE_GENERAL);
+ }
+
+ public function filterFrEduRneRespByLYCGT()
+ {
+ return $this->filterFrEduRneRespByNature(AttributesInterface::CODE_NATURE_LYCEE_GENERAL_ET_TECHNO);
+ }
+
+ public function filterFrEduRneRespByLP()
+ {
+ return $this->filterFrEduRneRespByNature(AttributesInterface::CODE_NATURE_LYCEE_GENERAL_ET_TECHNO);
+ }
+
+ public function filterFrEduRneRespByCLG()
+ {
+ return $this->filterFrEduRneRespByNature(AttributesInterface::CODE_NATURE_COLLEGE);
+ }
+
+ public function filterFrEduRneRespByLYCAG()
+ {
+ return $this->filterFrEduRneRespByNature(AttributesInterface::CODE_NATURE_LYCEE_AGRICOLE);
+ }
+
+ public function filterFrEduRneRespBySEGPA()
+ {
+ return $this->filterFrEduRneRespByNature(AttributesInterface::CODE_NATURE_SEGPA);
+ }
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Security/User/AuthUser.php b/console/skel/symfony-app/src/Session/AuthBundle/Security/User/AuthUser.php
new file mode 100755
index 0000000..0c6362b
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Security/User/AuthUser.php
@@ -0,0 +1,76 @@
+username = $username;
+ $this->salt = $salt;
+ $this->roles = $roles;
+ }
+
+ public function getRoles() {
+ return $this->roles;
+ }
+
+ public function setRoles($roles) {
+ return $this->roles = $roles;
+ }
+
+ public function addRole($role) {
+ return $this->roles[] = $role;
+ }
+
+ public function getPassword() {
+ return;
+ }
+
+ public function getSalt() {
+ return $this->salt;
+ }
+
+ public function getUsername() {
+ return $this->username;
+ }
+
+ public function eraseCredentials() {
+
+ }
+
+ public function isEqualTo(UserInterface $user) {
+ if (!$user instanceof AuthUser) {
+ return false;
+ }
+
+ if ($this->salt !== $user->getSalt()) {
+ return false;
+ }
+
+ if ($this->username !== $user->getUsername()) {
+ return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Security/User/AuthUserProvider.php b/console/skel/symfony-app/src/Session/AuthBundle/Security/User/AuthUserProvider.php
new file mode 100755
index 0000000..38fb807
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Security/User/AuthUserProvider.php
@@ -0,0 +1,58 @@
+config = $config;
+
+ if (!is_null($this->config['user_entity'])) {
+ $this->entity_user = "\\".$this->config['user_entity'];
+ } else {
+ $this->entity_user = "App\Session\AuthBundle\Security\User\AuthUser";
+ }
+ $this->authService = $authService;
+ }
+
+ public function loadUserByUsername($username) {
+ $entity_user = $this->entity_user;
+
+ return $this->authService->getUser($username);
+ }
+
+ private function _ctrlInstanceUser(UserInterface $user) {
+ $entity_user = $this->entity_user;
+
+ if (!$user instanceof $entity_user) {
+ throw new UnsupportedUserException(
+ sprintf('Instances of "%s" are not supported.', get_class($user))
+ );
+ }
+
+ return $user;
+ }
+
+ public function refreshUser(UserInterface $user) {
+ $user = $this->_ctrlInstanceUser($user);
+
+ return $this->loadUserByUsername($user->getUsername());
+ }
+
+ public function supportsClass($class) {
+ $entity_user = $this->entity_user;
+ return $this->entity_class === $class;
+ }
+
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/SessionAuthBundle.php b/console/skel/symfony-app/src/Session/AuthBundle/SessionAuthBundle.php
new file mode 100755
index 0000000..6e092b9
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/SessionAuthBundle.php
@@ -0,0 +1,9 @@
+request('GET', '/');
+
+ $this->assertContains('Hello World', $client->getResponse()->getContent());
+ }
+}
diff --git a/console/skel/symfony-app/src/Session/AuthBundle/Utils/Config.php b/console/skel/symfony-app/src/Session/AuthBundle/Utils/Config.php
new file mode 100755
index 0000000..d7bc9ea
--- /dev/null
+++ b/console/skel/symfony-app/src/Session/AuthBundle/Utils/Config.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\Cache;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\InvalidArgumentException;
+
+/**
+ * Covers most simple to advanced caching needs.
+ *
+ * @author Nicolas Grekas
+ */
+interface CacheInterface
+{
+ /**
+ * Fetches a value from the pool or computes it if not found.
+ *
+ * On cache misses, a callback is called that should return the missing value.
+ * This callback is given a PSR-6 CacheItemInterface instance corresponding to the
+ * requested key, that could be used e.g. for expiration control. It could also
+ * be an ItemInterface instance when its additional features are needed.
+ *
+ * @param string $key The key of the item to retrieve from the cache
+ * @param callable|CallbackInterface $callback Should return the computed value for the given key/item
+ * @param float|null $beta A float that, as it grows, controls the likeliness of triggering
+ * early expiration. 0 disables it, INF forces immediate expiration.
+ * The default (or providing null) is implementation dependent but should
+ * typically be 1.0, which should provide optimal stampede protection.
+ * See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration
+ * @param array &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()}
+ *
+ * @return mixed The value corresponding to the provided key
+ *
+ * @throws InvalidArgumentException When $key is not valid or when $beta is negative
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null);
+
+ /**
+ * Removes an item from the pool.
+ *
+ * @param string $key The key to delete
+ *
+ * @throws InvalidArgumentException When $key is not valid
+ *
+ * @return bool True if the item was successfully removed, false if there was any error
+ */
+ public function delete(string $key): bool;
+}
diff --git a/console/skel/symfony/cache-contracts/CacheTrait.php b/console/skel/symfony/cache-contracts/CacheTrait.php
new file mode 100644
index 0000000..355ea29
--- /dev/null
+++ b/console/skel/symfony/cache-contracts/CacheTrait.php
@@ -0,0 +1,76 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\Cache;
+
+use Psr\Cache\CacheItemPoolInterface;
+use Psr\Cache\InvalidArgumentException;
+use Psr\Log\LoggerInterface;
+
+/**
+ * An implementation of CacheInterface for PSR-6 CacheItemPoolInterface classes.
+ *
+ * @author Nicolas Grekas
+ */
+trait CacheTrait
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ return $this->doGet($this, $key, $callback, $beta, $metadata);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete(string $key): bool
+ {
+ return $this->deleteItem($key);
+ }
+
+ private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null, LoggerInterface $logger = null)
+ {
+ if (0 > $beta = $beta ?? 1.0) {
+ throw new class(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', \get_class($this), $beta)) extends \InvalidArgumentException implements InvalidArgumentException {
+ };
+ }
+
+ $item = $pool->getItem($key);
+ $recompute = !$item->isHit() || INF === $beta;
+ $metadata = $item instanceof ItemInterface ? $item->getMetadata() : [];
+
+ if (!$recompute && $metadata) {
+ $expiry = $metadata[ItemInterface::METADATA_EXPIRY] ?? false;
+ $ctime = $metadata[ItemInterface::METADATA_CTIME] ?? false;
+
+ if ($recompute = $ctime && $expiry && $expiry <= ($now = microtime(true)) - $ctime / 1000 * $beta * log(random_int(1, PHP_INT_MAX) / PHP_INT_MAX)) {
+ // force applying defaultLifetime to expiry
+ $item->expiresAt(null);
+ $logger && $logger->info('Item "{key}" elected for early recomputation {delta}s before its expiration', [
+ 'key' => $key,
+ 'delta' => sprintf('%.1f', $expiry - $now),
+ ]);
+ }
+ }
+
+ if ($recompute) {
+ $save = true;
+ $item->set($callback($item, $save));
+ if ($save) {
+ $pool->save($item);
+ }
+ }
+
+ return $item->get();
+ }
+}
diff --git a/console/skel/symfony/cache-contracts/CallbackInterface.php b/console/skel/symfony/cache-contracts/CallbackInterface.php
new file mode 100644
index 0000000..7dae2aa
--- /dev/null
+++ b/console/skel/symfony/cache-contracts/CallbackInterface.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\Cache;
+
+use Psr\Cache\CacheItemInterface;
+
+/**
+ * Computes and returns the cached value of an item.
+ *
+ * @author Nicolas Grekas
+ */
+interface CallbackInterface
+{
+ /**
+ * @param CacheItemInterface|ItemInterface $item The item to compute the value for
+ * @param bool &$save Should be set to false when the value should not be saved in the pool
+ *
+ * @return mixed The computed value for the passed item
+ */
+ public function __invoke(CacheItemInterface $item, bool &$save);
+}
diff --git a/console/skel/symfony/cache-contracts/ItemInterface.php b/console/skel/symfony/cache-contracts/ItemInterface.php
new file mode 100644
index 0000000..cbd7226
--- /dev/null
+++ b/console/skel/symfony/cache-contracts/ItemInterface.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\Cache;
+
+use Psr\Cache\CacheException;
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\InvalidArgumentException;
+
+/**
+ * Augments PSR-6's CacheItemInterface with support for tags and metadata.
+ *
+ * @author Nicolas Grekas
+ */
+interface ItemInterface extends CacheItemInterface
+{
+ /**
+ * References the Unix timestamp stating when the item will expire.
+ */
+ const METADATA_EXPIRY = 'expiry';
+
+ /**
+ * References the time the item took to be created, in milliseconds.
+ */
+ const METADATA_CTIME = 'ctime';
+
+ /**
+ * References the list of tags that were assigned to the item, as string[].
+ */
+ const METADATA_TAGS = 'tags';
+
+ /**
+ * Reserved characters that cannot be used in a key or tag.
+ */
+ const RESERVED_CHARACTERS = '{}()/\@:';
+
+ /**
+ * Adds a tag to a cache item.
+ *
+ * Tags are strings that follow the same validation rules as keys.
+ *
+ * @param string|string[] $tags A tag or array of tags
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException When $tag is not valid
+ * @throws CacheException When the item comes from a pool that is not tag-aware
+ */
+ public function tag($tags): self;
+
+ /**
+ * Returns a list of metadata info that were saved alongside with the cached value.
+ *
+ * See ItemInterface::METADATA_* consts for keys potentially found in the returned array.
+ */
+ public function getMetadata(): array;
+}
diff --git a/console/skel/symfony/cache-contracts/LICENSE b/console/skel/symfony/cache-contracts/LICENSE
new file mode 100644
index 0000000..3f853aa
--- /dev/null
+++ b/console/skel/symfony/cache-contracts/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/console/skel/symfony/cache-contracts/README.md b/console/skel/symfony/cache-contracts/README.md
new file mode 100644
index 0000000..58c589e
--- /dev/null
+++ b/console/skel/symfony/cache-contracts/README.md
@@ -0,0 +1,9 @@
+Symfony Cache Contracts
+=======================
+
+A set of abstractions extracted out of the Symfony components.
+
+Can be used to build on semantics that the Symfony components proved useful - and
+that already have battle tested implementations.
+
+See https://github.com/symfony/contracts/blob/master/README.md for more information.
diff --git a/console/skel/symfony/cache-contracts/TagAwareCacheInterface.php b/console/skel/symfony/cache-contracts/TagAwareCacheInterface.php
new file mode 100644
index 0000000..7c4cf11
--- /dev/null
+++ b/console/skel/symfony/cache-contracts/TagAwareCacheInterface.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\Cache;
+
+use Psr\Cache\InvalidArgumentException;
+
+/**
+ * Allows invalidating cached items using tags.
+ *
+ * @author Nicolas Grekas
+ */
+interface TagAwareCacheInterface extends CacheInterface
+{
+ /**
+ * Invalidates cached items using tags.
+ *
+ * When implemented on a PSR-6 pool, invalidation should not apply
+ * to deferred items. Instead, they should be committed as usual.
+ * This allows replacing old tagged values by new ones without
+ * race conditions.
+ *
+ * @param string[] $tags An array of tags to invalidate
+ *
+ * @return bool True on success
+ *
+ * @throws InvalidArgumentException When $tags is not valid
+ */
+ public function invalidateTags(array $tags);
+}
diff --git a/console/skel/symfony/cache-contracts/composer.json b/console/skel/symfony/cache-contracts/composer.json
new file mode 100644
index 0000000..c9cf8b5
--- /dev/null
+++ b/console/skel/symfony/cache-contracts/composer.json
@@ -0,0 +1,34 @@
+{
+ "name": "symfony/cache-contracts",
+ "type": "library",
+ "description": "Generic abstractions related to caching",
+ "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^7.2.5",
+ "psr/cache": "^1.0"
+ },
+ "suggest": {
+ "symfony/cache-implementation": ""
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Contracts\\Cache\\": "" }
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/.gitattributes b/console/skel/symfony/cache/.gitattributes
new file mode 100644
index 0000000..ebb9287
--- /dev/null
+++ b/console/skel/symfony/cache/.gitattributes
@@ -0,0 +1,3 @@
+/Tests export-ignore
+/phpunit.xml.dist export-ignore
+/.gitignore export-ignore
diff --git a/console/skel/symfony/cache/Adapter/AbstractAdapter.php b/console/skel/symfony/cache/Adapter/AbstractAdapter.php
new file mode 100644
index 0000000..98b42a4
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/AbstractAdapter.php
@@ -0,0 +1,203 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
+{
+ /**
+ * @internal
+ */
+ protected const NS_SEPARATOR = ':';
+
+ use AbstractAdapterTrait;
+ use ContractsTrait;
+
+ private static $apcuSupported;
+ private static $phpFilesSupported;
+
+ protected function __construct(string $namespace = '', int $defaultLifetime = 0)
+ {
+ $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::NS_SEPARATOR;
+ if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
+ throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
+ }
+ $this->createCacheItem = \Closure::bind(
+ static function ($key, $value, $isHit) use ($defaultLifetime) {
+ $item = new CacheItem();
+ $item->key = $key;
+ $item->value = $v = $value;
+ $item->isHit = $isHit;
+ $item->defaultLifetime = $defaultLifetime;
+ // Detect wrapped values that encode for their expiry and creation duration
+ // For compactness, these values are packed in the key of an array using
+ // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
+ if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
+ $item->value = $v[$k];
+ $v = unpack('Ve/Nc', substr($k, 1, -1));
+ $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
+ $item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
+ }
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ $getId = \Closure::fromCallable([$this, 'getId']);
+ $this->mergeByLifetime = \Closure::bind(
+ static function ($deferred, $namespace, &$expiredIds) use ($getId) {
+ $byLifetime = [];
+ $now = microtime(true);
+ $expiredIds = [];
+
+ foreach ($deferred as $key => $item) {
+ $key = (string) $key;
+ if (null === $item->expiry) {
+ $ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0;
+ } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
+ $expiredIds[] = $getId($key);
+ continue;
+ }
+ if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
+ unset($metadata[CacheItem::METADATA_TAGS]);
+ }
+ // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
+ $byLifetime[$ttl][$getId($key)] = $metadata ? ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item->value] : $item->value;
+ }
+
+ return $byLifetime;
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * Returns the best possible adapter that your runtime supports.
+ *
+ * Using ApcuAdapter makes system caches compatible with read-only filesystems.
+ *
+ * @param string $namespace
+ * @param int $defaultLifetime
+ * @param string $version
+ * @param string $directory
+ *
+ * @return AdapterInterface
+ */
+ public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null)
+ {
+ $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true);
+ if (null !== $logger) {
+ $opcache->setLogger($logger);
+ }
+
+ if (!self::$apcuSupported = self::$apcuSupported ?? ApcuAdapter::isSupported()) {
+ return $opcache;
+ }
+
+ $apcu = new ApcuAdapter($namespace, (int) $defaultLifetime / 5, $version);
+ if ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) {
+ $apcu->setLogger(new NullLogger());
+ } elseif (null !== $logger) {
+ $apcu->setLogger($logger);
+ }
+
+ return new ChainAdapter([$apcu, $opcache]);
+ }
+
+ public static function createConnection($dsn, array $options = [])
+ {
+ if (!\is_string($dsn)) {
+ throw new InvalidArgumentException(sprintf('The %s() method expect argument #1 to be string, %s given.', __METHOD__, \gettype($dsn)));
+ }
+ if (0 === strpos($dsn, 'redis:') || 0 === strpos($dsn, 'rediss:')) {
+ return RedisAdapter::createConnection($dsn, $options);
+ }
+ if (0 === strpos($dsn, 'memcached:')) {
+ return MemcachedAdapter::createConnection($dsn, $options);
+ }
+
+ throw new InvalidArgumentException(sprintf('Unsupported DSN: %s.', $dsn));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ $ok = true;
+ $byLifetime = $this->mergeByLifetime;
+ $byLifetime = $byLifetime($this->deferred, $this->namespace, $expiredIds);
+ $retry = $this->deferred = [];
+
+ if ($expiredIds) {
+ $this->doDelete($expiredIds);
+ }
+ foreach ($byLifetime as $lifetime => $values) {
+ try {
+ $e = $this->doSave($values, $lifetime);
+ } catch (\Exception $e) {
+ }
+ if (true === $e || [] === $e) {
+ continue;
+ }
+ if (\is_array($e) || 1 === \count($values)) {
+ foreach (\is_array($e) ? $e : array_keys($values) as $id) {
+ $ok = false;
+ $v = $values[$id];
+ $type = \is_object($v) ? \get_class($v) : \gettype($v);
+ $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
+ }
+ } else {
+ foreach ($values as $id => $v) {
+ $retry[$lifetime][] = $id;
+ }
+ }
+ }
+
+ // When bulk-save failed, retry each item individually
+ foreach ($retry as $lifetime => $ids) {
+ foreach ($ids as $id) {
+ try {
+ $v = $byLifetime[$lifetime][$id];
+ $e = $this->doSave([$id => $v], $lifetime);
+ } catch (\Exception $e) {
+ }
+ if (true === $e || [] === $e) {
+ continue;
+ }
+ $ok = false;
+ $type = \is_object($v) ? \get_class($v) : \gettype($v);
+ $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
+ }
+ }
+
+ return $ok;
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/AbstractTagAwareAdapter.php b/console/skel/symfony/cache/Adapter/AbstractTagAwareAdapter.php
new file mode 100644
index 0000000..10aca3b
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/AbstractTagAwareAdapter.php
@@ -0,0 +1,323 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Log\LoggerAwareInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Contracts\Cache\TagAwareCacheInterface;
+
+/**
+ * Abstract for native TagAware adapters.
+ *
+ * To keep info on tags, the tags are both serialized as part of cache value and provided as tag ids
+ * to Adapters on operations when needed for storage to doSave(), doDelete() & doInvalidate().
+ *
+ * @author Nicolas Grekas
+ * @author André Rømcke
+ *
+ * @internal
+ */
+abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, LoggerAwareInterface, ResettableInterface
+{
+ use AbstractAdapterTrait;
+ use ContractsTrait;
+
+ private const TAGS_PREFIX = "\0tags\0";
+
+ protected function __construct(string $namespace = '', int $defaultLifetime = 0)
+ {
+ $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
+ if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
+ throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
+ }
+ $this->createCacheItem = \Closure::bind(
+ static function ($key, $value, $isHit) use ($defaultLifetime) {
+ $item = new CacheItem();
+ $item->key = $key;
+ $item->defaultLifetime = $defaultLifetime;
+ $item->isTaggable = true;
+ // If structure does not match what we expect return item as is (no value and not a hit)
+ if (!\is_array($value) || !\array_key_exists('value', $value)) {
+ return $item;
+ }
+ $item->isHit = $isHit;
+ // Extract value, tags and meta data from the cache value
+ $item->value = $value['value'];
+ $item->metadata[CacheItem::METADATA_TAGS] = $value['tags'] ?? [];
+ if (isset($value['meta'])) {
+ // For compactness these values are packed, & expiry is offset to reduce size
+ $v = unpack('Ve/Nc', $value['meta']);
+ $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
+ $item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
+ }
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ $getId = \Closure::fromCallable([$this, 'getId']);
+ $tagPrefix = self::TAGS_PREFIX;
+ $this->mergeByLifetime = \Closure::bind(
+ static function ($deferred, &$expiredIds) use ($getId, $tagPrefix) {
+ $byLifetime = [];
+ $now = microtime(true);
+ $expiredIds = [];
+
+ foreach ($deferred as $key => $item) {
+ $key = (string) $key;
+ if (null === $item->expiry) {
+ $ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0;
+ } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
+ $expiredIds[] = $getId($key);
+ continue;
+ }
+ // Store Value and Tags on the cache value
+ if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
+ $value = ['value' => $item->value, 'tags' => $metadata[CacheItem::METADATA_TAGS]];
+ unset($metadata[CacheItem::METADATA_TAGS]);
+ } else {
+ $value = ['value' => $item->value, 'tags' => []];
+ }
+
+ if ($metadata) {
+ // For compactness, expiry and creation duration are packed, using magic numbers as separators
+ $value['meta'] = pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME]);
+ }
+
+ // Extract tag changes, these should be removed from values in doSave()
+ $value['tag-operations'] = ['add' => [], 'remove' => []];
+ $oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? [];
+ foreach (array_diff($value['tags'], $oldTags) as $addedTag) {
+ $value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag);
+ }
+ foreach (array_diff($oldTags, $value['tags']) as $removedTag) {
+ $value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag);
+ }
+
+ $byLifetime[$ttl][$getId($key)] = $value;
+ }
+
+ return $byLifetime;
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * Persists several cache items immediately.
+ *
+ * @param array $values The values to cache, indexed by their cache identifier
+ * @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning
+ * @param array[] $addTagData Hash where key is tag id, and array value is list of cache id's to add to tag
+ * @param array[] $removeTagData Hash where key is tag id, and array value is list of cache id's to remove to tag
+ *
+ * @return array The identifiers that failed to be cached or a boolean stating if caching succeeded or not
+ */
+ abstract protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array;
+
+ /**
+ * Removes multiple items from the pool and their corresponding tags.
+ *
+ * @param array $ids An array of identifiers that should be removed from the pool
+ *
+ * @return bool True if the items were successfully removed, false otherwise
+ */
+ abstract protected function doDelete(array $ids);
+
+ /**
+ * Removes relations between tags and deleted items.
+ *
+ * @param array $tagData Array of tag => key identifiers that should be removed from the pool
+ */
+ abstract protected function doDeleteTagRelations(array $tagData): bool;
+
+ /**
+ * Invalidates cached items using tags.
+ *
+ * @param string[] $tagIds An array of tags to invalidate, key is tag and value is tag id
+ *
+ * @return bool True on success
+ */
+ abstract protected function doInvalidate(array $tagIds): bool;
+
+ /**
+ * Delete items and yields the tags they were bound to.
+ */
+ protected function doDeleteYieldTags(array $ids): iterable
+ {
+ foreach ($this->doFetch($ids) as $id => $value) {
+ yield $id => \is_array($value) && \is_array($value['tags'] ?? null) ? $value['tags'] : [];
+ }
+
+ $this->doDelete($ids);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function commit(): bool
+ {
+ $ok = true;
+ $byLifetime = $this->mergeByLifetime;
+ $byLifetime = $byLifetime($this->deferred, $expiredIds);
+ $retry = $this->deferred = [];
+
+ if ($expiredIds) {
+ // Tags are not cleaned up in this case, however that is done on invalidateTags().
+ $this->doDelete($expiredIds);
+ }
+ foreach ($byLifetime as $lifetime => $values) {
+ try {
+ $values = $this->extractTagData($values, $addTagData, $removeTagData);
+ $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
+ } catch (\Exception $e) {
+ }
+ if (true === $e || [] === $e) {
+ continue;
+ }
+ if (\is_array($e) || 1 === \count($values)) {
+ foreach (\is_array($e) ? $e : array_keys($values) as $id) {
+ $ok = false;
+ $v = $values[$id];
+ $type = \is_object($v) ? \get_class($v) : \gettype($v);
+ $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
+ }
+ } else {
+ foreach ($values as $id => $v) {
+ $retry[$lifetime][] = $id;
+ }
+ }
+ }
+
+ // When bulk-save failed, retry each item individually
+ foreach ($retry as $lifetime => $ids) {
+ foreach ($ids as $id) {
+ try {
+ $v = $byLifetime[$lifetime][$id];
+ $values = $this->extractTagData([$id => $v], $addTagData, $removeTagData);
+ $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
+ } catch (\Exception $e) {
+ }
+ if (true === $e || [] === $e) {
+ continue;
+ }
+ $ok = false;
+ $type = \is_object($v) ? \get_class($v) : \gettype($v);
+ $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
+ }
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteItems(array $keys): bool
+ {
+ if (!$keys) {
+ return true;
+ }
+
+ $ok = true;
+ $ids = [];
+ $tagData = [];
+
+ foreach ($keys as $key) {
+ $ids[$key] = $this->getId($key);
+ unset($this->deferred[$key]);
+ }
+
+ try {
+ foreach ($this->doDeleteYieldTags(array_values($ids)) as $id => $tags) {
+ foreach ($tags as $tag) {
+ $tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id;
+ }
+ }
+ } catch (\Exception $e) {
+ $ok = false;
+ }
+
+ try {
+ if ((!$tagData || $this->doDeleteTagRelations($tagData)) && $ok) {
+ return true;
+ }
+ } catch (\Exception $e) {
+ }
+
+ // When bulk-delete failed, retry each item individually
+ foreach ($ids as $key => $id) {
+ try {
+ $e = null;
+ if ($this->doDelete([$id])) {
+ continue;
+ }
+ } catch (\Exception $e) {
+ }
+ $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]);
+ $ok = false;
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function invalidateTags(array $tags)
+ {
+ if (empty($tags)) {
+ return false;
+ }
+
+ $tagIds = [];
+ foreach (array_unique($tags) as $tag) {
+ $tagIds[] = $this->getId(self::TAGS_PREFIX.$tag);
+ }
+
+ if ($this->doInvalidate($tagIds)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Extracts tags operation data from $values set in mergeByLifetime, and returns values without it.
+ */
+ private function extractTagData(array $values, ?array &$addTagData, ?array &$removeTagData): array
+ {
+ $addTagData = $removeTagData = [];
+ foreach ($values as $id => $value) {
+ foreach ($value['tag-operations']['add'] as $tag => $tagId) {
+ $addTagData[$tagId][] = $id;
+ }
+
+ foreach ($value['tag-operations']['remove'] as $tag => $tagId) {
+ $removeTagData[$tagId][] = $id;
+ }
+
+ unset($values[$id]['tag-operations']);
+ }
+
+ return $values;
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/AdapterInterface.php b/console/skel/symfony/cache/Adapter/AdapterInterface.php
new file mode 100644
index 0000000..c40ae42
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/AdapterInterface.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\CacheItem;
+
+/**
+ * Interface for adapters managing instances of Symfony's CacheItem.
+ *
+ * @author Kévin Dunglas
+ */
+interface AdapterInterface extends CacheItemPoolInterface
+{
+ /**
+ * {@inheritdoc}
+ *
+ * @return CacheItem
+ */
+ public function getItem($key);
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return \Traversable|CacheItem[]
+ */
+ public function getItems(array $keys = []);
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
+ */
+ public function clear(/*string $prefix = ''*/);
+}
diff --git a/console/skel/symfony/cache/Adapter/ApcuAdapter.php b/console/skel/symfony/cache/Adapter/ApcuAdapter.php
new file mode 100644
index 0000000..7db3956
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/ApcuAdapter.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Symfony\Component\Cache\Traits\ApcuTrait;
+
+class ApcuAdapter extends AbstractAdapter
+{
+ use ApcuTrait;
+
+ /**
+ * @throws CacheException if APCu is not enabled
+ */
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null)
+ {
+ $this->init($namespace, $defaultLifetime, $version);
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/ArrayAdapter.php b/console/skel/symfony/cache/Adapter/ArrayAdapter.php
new file mode 100644
index 0000000..d93dcbd
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/ArrayAdapter.php
@@ -0,0 +1,171 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Log\LoggerAwareInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ArrayTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
+{
+ use ArrayTrait;
+
+ private $createCacheItem;
+
+ /**
+ * @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
+ */
+ public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true)
+ {
+ $this->storeSerialized = $storeSerialized;
+ $this->createCacheItem = \Closure::bind(
+ static function ($key, $value, $isHit) use ($defaultLifetime) {
+ $item = new CacheItem();
+ $item->key = $key;
+ $item->value = $value;
+ $item->isHit = $isHit;
+ $item->defaultLifetime = $defaultLifetime;
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ $item = $this->getItem($key);
+ $metadata = $item->getMetadata();
+
+ // ArrayAdapter works in memory, we don't care about stampede protection
+ if (INF === $beta || !$item->isHit()) {
+ $save = true;
+ $this->save($item->set($callback($item, $save)));
+ }
+
+ return $item->get();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ if (!$isHit = $this->hasItem($key)) {
+ $this->values[$key] = $value = null;
+ } else {
+ $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
+ }
+ $f = $this->createCacheItem;
+
+ return $f($key, $value, $isHit);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ foreach ($keys as $key) {
+ if (!\is_string($key) || !isset($this->expiries[$key])) {
+ CacheItem::validateKey($key);
+ }
+ }
+
+ return $this->generateItems($keys, microtime(true), $this->createCacheItem);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItems(array $keys)
+ {
+ foreach ($keys as $key) {
+ $this->deleteItem($key);
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ if (!$item instanceof CacheItem) {
+ return false;
+ }
+ $item = (array) $item;
+ $key = $item["\0*\0key"];
+ $value = $item["\0*\0value"];
+ $expiry = $item["\0*\0expiry"];
+
+ if (null !== $expiry && $expiry <= microtime(true)) {
+ $this->deleteItem($key);
+
+ return true;
+ }
+ if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
+ return false;
+ }
+ if (null === $expiry && 0 < $item["\0*\0defaultLifetime"]) {
+ $expiry = microtime(true) + $item["\0*\0defaultLifetime"];
+ }
+
+ $this->values[$key] = $value;
+ $this->expiries[$key] = null !== $expiry ? $expiry : PHP_INT_MAX;
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ return $this->save($item);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete(string $key): bool
+ {
+ return $this->deleteItem($key);
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/ChainAdapter.php b/console/skel/symfony/cache/Adapter/ChainAdapter.php
new file mode 100644
index 0000000..63e97a8
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/ChainAdapter.php
@@ -0,0 +1,332 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * Chains several adapters together.
+ *
+ * Cached items are fetched from the first adapter having them in its data store.
+ * They are saved and deleted in all adapters at once.
+ *
+ * @author Kévin Dunglas
+ */
+class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
+{
+ use ContractsTrait;
+
+ private $adapters = [];
+ private $adapterCount;
+ private $syncItem;
+
+ /**
+ * @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items
+ * @param int $defaultLifetime The default lifetime of items propagated from lower adapters to upper ones
+ */
+ public function __construct(array $adapters, int $defaultLifetime = 0)
+ {
+ if (!$adapters) {
+ throw new InvalidArgumentException('At least one adapter must be specified.');
+ }
+
+ foreach ($adapters as $adapter) {
+ if (!$adapter instanceof CacheItemPoolInterface) {
+ throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($adapter), CacheItemPoolInterface::class));
+ }
+
+ if ($adapter instanceof AdapterInterface) {
+ $this->adapters[] = $adapter;
+ } else {
+ $this->adapters[] = new ProxyAdapter($adapter);
+ }
+ }
+ $this->adapterCount = \count($this->adapters);
+
+ $this->syncItem = \Closure::bind(
+ static function ($sourceItem, $item, $sourceMetadata = null) use ($defaultLifetime) {
+ $sourceItem->isTaggable = false;
+ $sourceMetadata = $sourceMetadata ?? $sourceItem->metadata;
+ unset($sourceMetadata[CacheItem::METADATA_TAGS]);
+
+ $item->value = $sourceItem->value;
+ $item->expiry = $sourceMetadata[CacheItem::METADATA_EXPIRY] ?? $sourceItem->expiry;
+ $item->isHit = $sourceItem->isHit;
+ $item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata;
+
+ if (0 < $sourceItem->defaultLifetime && $sourceItem->defaultLifetime < $defaultLifetime) {
+ $defaultLifetime = $sourceItem->defaultLifetime;
+ }
+ if (0 < $defaultLifetime && ($item->defaultLifetime <= 0 || $defaultLifetime < $item->defaultLifetime)) {
+ $item->defaultLifetime = $defaultLifetime;
+ }
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ $lastItem = null;
+ $i = 0;
+ $wrap = function (CacheItem $item = null) use ($key, $callback, $beta, &$wrap, &$i, &$lastItem, &$metadata) {
+ $adapter = $this->adapters[$i];
+ if (isset($this->adapters[++$i])) {
+ $callback = $wrap;
+ $beta = INF === $beta ? INF : 0;
+ }
+ if ($adapter instanceof CacheInterface) {
+ $value = $adapter->get($key, $callback, $beta, $metadata);
+ } else {
+ $value = $this->doGet($adapter, $key, $callback, $beta, $metadata);
+ }
+ if (null !== $item) {
+ ($this->syncItem)($lastItem = $lastItem ?? $item, $item, $metadata);
+ }
+
+ return $value;
+ };
+
+ return $wrap();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ $syncItem = $this->syncItem;
+ $misses = [];
+
+ foreach ($this->adapters as $i => $adapter) {
+ $item = $adapter->getItem($key);
+
+ if ($item->isHit()) {
+ while (0 <= --$i) {
+ $this->adapters[$i]->save($syncItem($item, $misses[$i]));
+ }
+
+ return $item;
+ }
+
+ $misses[$i] = $item;
+ }
+
+ return $item;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ return $this->generateItems($this->adapters[0]->getItems($keys), 0);
+ }
+
+ private function generateItems(iterable $items, int $adapterIndex)
+ {
+ $missing = [];
+ $misses = [];
+ $nextAdapterIndex = $adapterIndex + 1;
+ $nextAdapter = isset($this->adapters[$nextAdapterIndex]) ? $this->adapters[$nextAdapterIndex] : null;
+
+ foreach ($items as $k => $item) {
+ if (!$nextAdapter || $item->isHit()) {
+ yield $k => $item;
+ } else {
+ $missing[] = $k;
+ $misses[$k] = $item;
+ }
+ }
+
+ if ($missing) {
+ $syncItem = $this->syncItem;
+ $adapter = $this->adapters[$adapterIndex];
+ $items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex);
+
+ foreach ($items as $k => $item) {
+ if ($item->isHit()) {
+ $adapter->save($syncItem($item, $misses[$k]));
+ }
+
+ yield $k => $item;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ foreach ($this->adapters as $adapter) {
+ if ($adapter->hasItem($key)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
+ */
+ public function clear(/*string $prefix = ''*/)
+ {
+ $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
+ $cleared = true;
+ $i = $this->adapterCount;
+
+ while ($i--) {
+ if ($this->adapters[$i] instanceof AdapterInterface) {
+ $cleared = $this->adapters[$i]->clear($prefix) && $cleared;
+ } else {
+ $cleared = $this->adapters[$i]->clear() && $cleared;
+ }
+ }
+
+ return $cleared;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItem($key)
+ {
+ $deleted = true;
+ $i = $this->adapterCount;
+
+ while ($i--) {
+ $deleted = $this->adapters[$i]->deleteItem($key) && $deleted;
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItems(array $keys)
+ {
+ $deleted = true;
+ $i = $this->adapterCount;
+
+ while ($i--) {
+ $deleted = $this->adapters[$i]->deleteItems($keys) && $deleted;
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ $saved = true;
+ $i = $this->adapterCount;
+
+ while ($i--) {
+ $saved = $this->adapters[$i]->save($item) && $saved;
+ }
+
+ return $saved;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ $saved = true;
+ $i = $this->adapterCount;
+
+ while ($i--) {
+ $saved = $this->adapters[$i]->saveDeferred($item) && $saved;
+ }
+
+ return $saved;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ $committed = true;
+ $i = $this->adapterCount;
+
+ while ($i--) {
+ $committed = $this->adapters[$i]->commit() && $committed;
+ }
+
+ return $committed;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prune()
+ {
+ $pruned = true;
+
+ foreach ($this->adapters as $adapter) {
+ if ($adapter instanceof PruneableInterface) {
+ $pruned = $adapter->prune() && $pruned;
+ }
+ }
+
+ return $pruned;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ foreach ($this->adapters as $adapter) {
+ if ($adapter instanceof ResetInterface) {
+ $adapter->reset();
+ }
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/DoctrineAdapter.php b/console/skel/symfony/cache/Adapter/DoctrineAdapter.php
new file mode 100644
index 0000000..75ae4cb
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/DoctrineAdapter.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Doctrine\Common\Cache\CacheProvider;
+use Symfony\Component\Cache\Traits\DoctrineTrait;
+
+class DoctrineAdapter extends AbstractAdapter
+{
+ use DoctrineTrait;
+
+ public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0)
+ {
+ parent::__construct('', $defaultLifetime);
+ $this->provider = $provider;
+ $provider->setNamespace($namespace);
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/FilesystemAdapter.php b/console/skel/symfony/cache/Adapter/FilesystemAdapter.php
new file mode 100644
index 0000000..7185dd4
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/FilesystemAdapter.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\FilesystemTrait;
+
+class FilesystemAdapter extends AbstractAdapter implements PruneableInterface
+{
+ use FilesystemTrait;
+
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
+ {
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+ parent::__construct('', $defaultLifetime);
+ $this->init($namespace, $directory);
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/FilesystemTagAwareAdapter.php b/console/skel/symfony/cache/Adapter/FilesystemTagAwareAdapter.php
new file mode 100644
index 0000000..d9a1ad3
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/FilesystemTagAwareAdapter.php
@@ -0,0 +1,239 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Marshaller\TagAwareMarshaller;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\FilesystemTrait;
+
+/**
+ * Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls.
+ *
+ * @author Nicolas Grekas
+ * @author André Rømcke
+ */
+class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface
+{
+ use FilesystemTrait {
+ doClear as private doClearCache;
+ doSave as private doSaveCache;
+ }
+
+ /**
+ * Folder used for tag symlinks.
+ */
+ private const TAG_FOLDER = 'tags';
+
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
+ {
+ $this->marshaller = new TagAwareMarshaller($marshaller);
+ parent::__construct('', $defaultLifetime);
+ $this->init($namespace, $directory);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear($namespace)
+ {
+ $ok = $this->doClearCache($namespace);
+
+ if ('' !== $namespace) {
+ return $ok;
+ }
+
+ set_error_handler(static function () {});
+ $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+ try {
+ foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) {
+ if (rename($dir, $renamed = substr_replace($dir, bin2hex(random_bytes(4)), -8))) {
+ $dir = $renamed.\DIRECTORY_SEPARATOR;
+ } else {
+ $dir .= \DIRECTORY_SEPARATOR;
+ $renamed = null;
+ }
+
+ for ($i = 0; $i < 38; ++$i) {
+ if (!file_exists($dir.$chars[$i])) {
+ continue;
+ }
+ for ($j = 0; $j < 38; ++$j) {
+ if (!file_exists($d = $dir.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) {
+ continue;
+ }
+ foreach (scandir($d, SCANDIR_SORT_NONE) ?: [] as $link) {
+ if ('.' !== $link && '..' !== $link && (null !== $renamed || !realpath($d.\DIRECTORY_SEPARATOR.$link))) {
+ unlink($d.\DIRECTORY_SEPARATOR.$link);
+ }
+ }
+ null === $renamed ?: rmdir($d);
+ }
+ null === $renamed ?: rmdir($dir.$chars[$i]);
+ }
+ null === $renamed ?: rmdir($renamed);
+ }
+ } finally {
+ restore_error_handler();
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array
+ {
+ $failed = $this->doSaveCache($values, $lifetime);
+
+ // Add Tags as symlinks
+ foreach ($addTagData as $tagId => $ids) {
+ $tagFolder = $this->getTagFolder($tagId);
+ foreach ($ids as $id) {
+ if ($failed && \in_array($id, $failed, true)) {
+ continue;
+ }
+
+ $file = $this->getFile($id);
+
+ if (!@symlink($file, $this->getFile($id, true, $tagFolder))) {
+ @unlink($file);
+ $failed[] = $id;
+ }
+ }
+ }
+
+ // Unlink removed Tags
+ foreach ($removeTagData as $tagId => $ids) {
+ $tagFolder = $this->getTagFolder($tagId);
+ foreach ($ids as $id) {
+ if ($failed && \in_array($id, $failed, true)) {
+ continue;
+ }
+
+ @unlink($this->getFile($id, false, $tagFolder));
+ }
+ }
+
+ return $failed;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDeleteYieldTags(array $ids): iterable
+ {
+ foreach ($ids as $id) {
+ $file = $this->getFile($id);
+ if (!file_exists($file) || !$h = @fopen($file, 'rb')) {
+ continue;
+ }
+
+ if ((\PHP_VERSION_ID >= 70300 || '\\' !== \DIRECTORY_SEPARATOR) && !@unlink($file)) {
+ fclose($h);
+ continue;
+ }
+
+ $meta = explode("\n", fread($h, 4096), 3)[2] ?? '';
+
+ // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
+ if (13 < \strlen($meta) && "\x9D" === $meta[0] && "\0" === $meta[5] && "\x5F" === $meta[9]) {
+ $meta[9] = "\0";
+ $tagLen = unpack('Nlen', $meta, 9)['len'];
+ $meta = substr($meta, 13, $tagLen);
+
+ if (0 < $tagLen -= \strlen($meta)) {
+ $meta .= fread($h, $tagLen);
+ }
+
+ try {
+ yield $id => '' === $meta ? [] : $this->marshaller->unmarshall($meta);
+ } catch (\Exception $e) {
+ yield $id => [];
+ }
+ }
+
+ fclose($h);
+
+ if (\PHP_VERSION_ID < 70300 && '\\' === \DIRECTORY_SEPARATOR) {
+ @unlink($file);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDeleteTagRelations(array $tagData): bool
+ {
+ foreach ($tagData as $tagId => $idList) {
+ $tagFolder = $this->getTagFolder($tagId);
+ foreach ($idList as $id) {
+ @unlink($this->getFile($id, false, $tagFolder));
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doInvalidate(array $tagIds): bool
+ {
+ foreach ($tagIds as $tagId) {
+ if (!file_exists($tagFolder = $this->getTagFolder($tagId))) {
+ continue;
+ }
+
+ set_error_handler(static function () {});
+
+ try {
+ if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -9))) {
+ $tagFolder = $renamed.\DIRECTORY_SEPARATOR;
+ } else {
+ $renamed = null;
+ }
+
+ foreach ($this->scanHashDir($tagFolder) as $itemLink) {
+ unlink(realpath($itemLink) ?: $itemLink);
+ unlink($itemLink);
+ }
+
+ if (null === $renamed) {
+ continue;
+ }
+
+ $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+ for ($i = 0; $i < 38; ++$i) {
+ for ($j = 0; $j < 38; ++$j) {
+ rmdir($tagFolder.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j]);
+ }
+ rmdir($tagFolder.$chars[$i]);
+ }
+ rmdir($renamed);
+ } finally {
+ restore_error_handler();
+ }
+ }
+
+ return true;
+ }
+
+ private function getTagFolder(string $tagId): string
+ {
+ return $this->getFile($tagId, false, $this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR;
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/MemcachedAdapter.php b/console/skel/symfony/cache/Adapter/MemcachedAdapter.php
new file mode 100644
index 0000000..b678bb5
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/MemcachedAdapter.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Traits\MemcachedTrait;
+
+class MemcachedAdapter extends AbstractAdapter
+{
+ use MemcachedTrait;
+
+ protected $maxIdLength = 250;
+
+ /**
+ * Using a MemcachedAdapter with a TagAwareAdapter for storing tags is discouraged.
+ * Using a RedisAdapter is recommended instead. If you cannot do otherwise, be aware that:
+ * - the Memcached::OPT_BINARY_PROTOCOL must be enabled
+ * (that's the default when using MemcachedAdapter::createConnection());
+ * - tags eviction by Memcached's LRU algorithm will break by-tags invalidation;
+ * your Memcached memory should be large enough to never trigger LRU.
+ *
+ * Using a MemcachedAdapter as a pure items store is fine.
+ */
+ public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+ {
+ $this->init($client, $namespace, $defaultLifetime, $marshaller);
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/NullAdapter.php b/console/skel/symfony/cache/Adapter/NullAdapter.php
new file mode 100644
index 0000000..a2fdd36
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/NullAdapter.php
@@ -0,0 +1,156 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Contracts\Cache\CacheInterface;
+
+/**
+ * @author Titouan Galopin
+ */
+class NullAdapter implements AdapterInterface, CacheInterface
+{
+ private $createCacheItem;
+
+ public function __construct()
+ {
+ $this->createCacheItem = \Closure::bind(
+ function ($key) {
+ $item = new CacheItem();
+ $item->key = $key;
+ $item->isHit = false;
+
+ return $item;
+ },
+ $this,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ $save = true;
+
+ return $callback(($this->createCacheItem)($key), $save);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ $f = $this->createCacheItem;
+
+ return $f($key);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ return $this->generateItems($keys);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
+ */
+ public function clear(/*string $prefix = ''*/)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItem($key)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItems(array $keys)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete(string $key): bool
+ {
+ return $this->deleteItem($key);
+ }
+
+ private function generateItems(array $keys)
+ {
+ $f = $this->createCacheItem;
+
+ foreach ($keys as $key) {
+ yield $key => $f($key);
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/PdoAdapter.php b/console/skel/symfony/cache/Adapter/PdoAdapter.php
new file mode 100644
index 0000000..d118736
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/PdoAdapter.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Doctrine\DBAL\Connection;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\PdoTrait;
+
+class PdoAdapter extends AbstractAdapter implements PruneableInterface
+{
+ use PdoTrait;
+
+ protected $maxIdLength = 255;
+
+ /**
+ * You can either pass an existing database connection as PDO instance or
+ * a Doctrine DBAL Connection or a DSN string that will be used to
+ * lazy-connect to the database when the cache is actually used.
+ *
+ * When a Doctrine DBAL Connection is passed, the cache table is created
+ * automatically when possible. Otherwise, use the createTable() method.
+ *
+ * List of available options:
+ * * db_table: The name of the table [default: cache_items]
+ * * db_id_col: The column where to store the cache id [default: item_id]
+ * * db_data_col: The column where to store the cache data [default: item_data]
+ * * db_lifetime_col: The column where to store the lifetime [default: item_lifetime]
+ * * db_time_col: The column where to store the timestamp [default: item_time]
+ * * db_username: The username when lazy-connect [default: '']
+ * * db_password: The password when lazy-connect [default: '']
+ * * db_connection_options: An array of driver-specific connection options [default: []]
+ *
+ * @param \PDO|Connection|string $connOrDsn a \PDO or Connection instance or DSN string or null
+ *
+ * @throws InvalidArgumentException When first argument is not PDO nor Connection nor string
+ * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
+ * @throws InvalidArgumentException When namespace contains invalid characters
+ */
+ public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
+ {
+ $this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/PhpArrayAdapter.php b/console/skel/symfony/cache/Adapter/PhpArrayAdapter.php
new file mode 100644
index 0000000..b4f13d1
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/PhpArrayAdapter.php
@@ -0,0 +1,332 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Component\Cache\Traits\PhpArrayTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+/**
+ * Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0.
+ * Warmed up items are read-only and run-time discovered items are cached using a fallback adapter.
+ *
+ * @author Titouan Galopin
+ * @author Nicolas Grekas
+ */
+class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
+{
+ use PhpArrayTrait;
+ use ContractsTrait;
+
+ private $createCacheItem;
+
+ /**
+ * @param string $file The PHP file were values are cached
+ * @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit
+ */
+ public function __construct(string $file, AdapterInterface $fallbackPool)
+ {
+ $this->file = $file;
+ $this->pool = $fallbackPool;
+ $this->createCacheItem = \Closure::bind(
+ static function ($key, $value, $isHit) {
+ $item = new CacheItem();
+ $item->key = $key;
+ $item->value = $value;
+ $item->isHit = $isHit;
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * This adapter takes advantage of how PHP stores arrays in its latest versions.
+ *
+ * @param string $file The PHP file were values are cached
+ * @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit
+ *
+ * @return CacheItemPoolInterface
+ */
+ public static function create($file, CacheItemPoolInterface $fallbackPool)
+ {
+ if (!$fallbackPool instanceof AdapterInterface) {
+ $fallbackPool = new ProxyAdapter($fallbackPool);
+ }
+
+ return new static($file, $fallbackPool);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ if (null === $this->values) {
+ $this->initialize();
+ }
+ if (!isset($this->keys[$key])) {
+ get_from_pool:
+ if ($this->pool instanceof CacheInterface) {
+ return $this->pool->get($key, $callback, $beta, $metadata);
+ }
+
+ return $this->doGet($this->pool, $key, $callback, $beta, $metadata);
+ }
+ $value = $this->values[$this->keys[$key]];
+
+ if ('N;' === $value) {
+ return null;
+ }
+ try {
+ if ($value instanceof \Closure) {
+ return $value();
+ }
+ } catch (\Throwable $e) {
+ unset($this->keys[$key]);
+ goto get_from_pool;
+ }
+
+ return $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ }
+ if (null === $this->values) {
+ $this->initialize();
+ }
+ if (!isset($this->keys[$key])) {
+ return $this->pool->getItem($key);
+ }
+
+ $value = $this->values[$this->keys[$key]];
+ $isHit = true;
+
+ if ('N;' === $value) {
+ $value = null;
+ } elseif ($value instanceof \Closure) {
+ try {
+ $value = $value();
+ } catch (\Throwable $e) {
+ $value = null;
+ $isHit = false;
+ }
+ }
+
+ $f = $this->createCacheItem;
+
+ return $f($key, $value, $isHit);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ foreach ($keys as $key) {
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ }
+ }
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ return $this->generateItems($keys);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ }
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ return isset($this->keys[$key]) || $this->pool->hasItem($key);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItem($key)
+ {
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ }
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ return !isset($this->keys[$key]) && $this->pool->deleteItem($key);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItems(array $keys)
+ {
+ $deleted = true;
+ $fallbackKeys = [];
+
+ foreach ($keys as $key) {
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ }
+
+ if (isset($this->keys[$key])) {
+ $deleted = false;
+ } else {
+ $fallbackKeys[] = $key;
+ }
+ }
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ if ($fallbackKeys) {
+ $deleted = $this->pool->deleteItems($fallbackKeys) && $deleted;
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ return !isset($this->keys[$item->getKey()]) && $this->pool->save($item);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ return $this->pool->commit();
+ }
+
+ private function generateItems(array $keys): \Generator
+ {
+ $f = $this->createCacheItem;
+ $fallbackKeys = [];
+
+ foreach ($keys as $key) {
+ if (isset($this->keys[$key])) {
+ $value = $this->values[$this->keys[$key]];
+
+ if ('N;' === $value) {
+ yield $key => $f($key, null, true);
+ } elseif ($value instanceof \Closure) {
+ try {
+ yield $key => $f($key, $value(), true);
+ } catch (\Throwable $e) {
+ yield $key => $f($key, null, false);
+ }
+ } else {
+ yield $key => $f($key, $value, true);
+ }
+ } else {
+ $fallbackKeys[] = $key;
+ }
+ }
+
+ if ($fallbackKeys) {
+ yield from $this->pool->getItems($fallbackKeys);
+ }
+ }
+
+ /**
+ * @throws \ReflectionException When $class is not found and is required
+ *
+ * @internal to be removed in Symfony 5.0
+ */
+ public static function throwOnRequiredClass($class)
+ {
+ $e = new \ReflectionException("Class $class does not exist");
+ $trace = debug_backtrace();
+ $autoloadFrame = [
+ 'function' => 'spl_autoload_call',
+ 'args' => [$class],
+ ];
+ $i = 1 + array_search($autoloadFrame, $trace, true);
+
+ if (isset($trace[$i]['function']) && !isset($trace[$i]['class'])) {
+ switch ($trace[$i]['function']) {
+ case 'get_class_methods':
+ case 'get_class_vars':
+ case 'get_parent_class':
+ case 'is_a':
+ case 'is_subclass_of':
+ case 'class_exists':
+ case 'class_implements':
+ case 'class_parents':
+ case 'trait_exists':
+ case 'defined':
+ case 'interface_exists':
+ case 'method_exists':
+ case 'property_exists':
+ case 'is_callable':
+ return;
+ }
+ }
+
+ throw $e;
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/PhpFilesAdapter.php b/console/skel/symfony/cache/Adapter/PhpFilesAdapter.php
new file mode 100644
index 0000000..10938a0
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/PhpFilesAdapter.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\PhpFilesTrait;
+
+class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
+{
+ use PhpFilesTrait;
+
+ /**
+ * @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire.
+ * Doing so is encouraged because it fits perfectly OPcache's memory model.
+ *
+ * @throws CacheException if OPcache is not enabled
+ */
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
+ {
+ $this->appendOnly = $appendOnly;
+ self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
+ parent::__construct('', $defaultLifetime);
+ $this->init($namespace, $directory);
+ $this->includeHandler = static function ($type, $msg, $file, $line) {
+ throw new \ErrorException($msg, 0, $type, $file, $line);
+ };
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/ProxyAdapter.php b/console/skel/symfony/cache/Adapter/ProxyAdapter.php
new file mode 100644
index 0000000..bccafb3
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/ProxyAdapter.php
@@ -0,0 +1,269 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Component\Cache\Traits\ProxyTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
+{
+ use ProxyTrait;
+ use ContractsTrait;
+
+ private $namespace;
+ private $namespaceLen;
+ private $createCacheItem;
+ private $setInnerItem;
+ private $poolHash;
+
+ public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0)
+ {
+ $this->pool = $pool;
+ $this->poolHash = $poolHash = spl_object_hash($pool);
+ $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace);
+ $this->namespaceLen = \strlen($namespace);
+ $this->createCacheItem = \Closure::bind(
+ static function ($key, $innerItem) use ($defaultLifetime, $poolHash) {
+ $item = new CacheItem();
+ $item->key = $key;
+
+ if (null === $innerItem) {
+ return $item;
+ }
+
+ $item->value = $v = $innerItem->get();
+ $item->isHit = $innerItem->isHit();
+ $item->innerItem = $innerItem;
+ $item->defaultLifetime = $defaultLifetime;
+ $item->poolHash = $poolHash;
+
+ // Detect wrapped values that encode for their expiry and creation duration
+ // For compactness, these values are packed in the key of an array using
+ // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
+ if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
+ $item->value = $v[$k];
+ $v = unpack('Ve/Nc', substr($k, 1, -1));
+ $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
+ $item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
+ } elseif ($innerItem instanceof CacheItem) {
+ $item->metadata = $innerItem->metadata;
+ }
+ $innerItem->set(null);
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ $this->setInnerItem = \Closure::bind(
+ /**
+ * @param array $item A CacheItem cast to (array); accessing protected properties requires adding the "\0*\0" PHP prefix
+ */
+ static function (CacheItemInterface $innerItem, array $item) {
+ // Tags are stored separately, no need to account for them when considering this item's newly set metadata
+ if (isset(($metadata = $item["\0*\0newMetadata"])[CacheItem::METADATA_TAGS])) {
+ unset($metadata[CacheItem::METADATA_TAGS]);
+ }
+ if ($metadata) {
+ // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
+ $item["\0*\0value"] = ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item["\0*\0value"]];
+ }
+ $innerItem->set($item["\0*\0value"]);
+ $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6f', $item["\0*\0expiry"])) : null);
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ if (!$this->pool instanceof CacheInterface) {
+ return $this->doGet($this, $key, $callback, $beta, $metadata);
+ }
+
+ return $this->pool->get($this->getId($key), function ($innerItem, bool &$save) use ($key, $callback) {
+ $item = ($this->createCacheItem)($key, $innerItem);
+ $item->set($value = $callback($item, $save));
+ ($this->setInnerItem)($innerItem, (array) $item);
+
+ return $value;
+ }, $beta, $metadata);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ $f = $this->createCacheItem;
+ $item = $this->pool->getItem($this->getId($key));
+
+ return $f($key, $item);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ if ($this->namespaceLen) {
+ foreach ($keys as $i => $key) {
+ $keys[$i] = $this->getId($key);
+ }
+ }
+
+ return $this->generateItems($this->pool->getItems($keys));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ return $this->pool->hasItem($this->getId($key));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
+ */
+ public function clear(/*string $prefix = ''*/)
+ {
+ $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
+
+ if ($this->pool instanceof AdapterInterface) {
+ return $this->pool->clear($this->namespace.$prefix);
+ }
+
+ return $this->pool->clear();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItem($key)
+ {
+ return $this->pool->deleteItem($this->getId($key));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItems(array $keys)
+ {
+ if ($this->namespaceLen) {
+ foreach ($keys as $i => $key) {
+ $keys[$i] = $this->getId($key);
+ }
+ }
+
+ return $this->pool->deleteItems($keys);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ return $this->doSave($item, __FUNCTION__);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ return $this->doSave($item, __FUNCTION__);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ return $this->pool->commit();
+ }
+
+ private function doSave(CacheItemInterface $item, string $method)
+ {
+ if (!$item instanceof CacheItem) {
+ return false;
+ }
+ $item = (array) $item;
+ if (null === $item["\0*\0expiry"] && 0 < $item["\0*\0defaultLifetime"]) {
+ $item["\0*\0expiry"] = microtime(true) + $item["\0*\0defaultLifetime"];
+ }
+
+ if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) {
+ $innerItem = $item["\0*\0innerItem"];
+ } elseif ($this->pool instanceof AdapterInterface) {
+ // this is an optimization specific for AdapterInterface implementations
+ // so we can save a round-trip to the backend by just creating a new item
+ $f = $this->createCacheItem;
+ $innerItem = $f($this->namespace.$item["\0*\0key"], null);
+ } else {
+ $innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]);
+ }
+
+ ($this->setInnerItem)($innerItem, $item);
+
+ return $this->pool->$method($innerItem);
+ }
+
+ private function generateItems(iterable $items)
+ {
+ $f = $this->createCacheItem;
+
+ foreach ($items as $key => $item) {
+ if ($this->namespaceLen) {
+ $key = substr($key, $this->namespaceLen);
+ }
+
+ yield $key => $f($key, $item);
+ }
+ }
+
+ private function getId($key): string
+ {
+ CacheItem::validateKey($key);
+
+ return $this->namespace.$key;
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/Psr16Adapter.php b/console/skel/symfony/cache/Adapter/Psr16Adapter.php
new file mode 100644
index 0000000..bb38871
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/Psr16Adapter.php
@@ -0,0 +1,86 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\SimpleCache\CacheInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ProxyTrait;
+
+/**
+ * Turns a PSR-16 cache into a PSR-6 one.
+ *
+ * @author Nicolas Grekas
+ */
+class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface
+{
+ /**
+ * @internal
+ */
+ protected const NS_SEPARATOR = '_';
+
+ use ProxyTrait;
+
+ private $miss;
+
+ public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0)
+ {
+ parent::__construct($namespace, $defaultLifetime);
+
+ $this->pool = $pool;
+ $this->miss = new \stdClass();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) {
+ if ($this->miss !== $value) {
+ yield $key => $value;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave($id)
+ {
+ return $this->pool->has($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear($namespace)
+ {
+ return $this->pool->clear();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ return $this->pool->deleteMultiple($ids);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, $lifetime)
+ {
+ return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/RedisAdapter.php b/console/skel/symfony/cache/Adapter/RedisAdapter.php
new file mode 100644
index 0000000..5c49f7a
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/RedisAdapter.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Traits\RedisTrait;
+
+class RedisAdapter extends AbstractAdapter
+{
+ use RedisTrait;
+
+ /**
+ * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient The redis client
+ * @param string $namespace The default namespace
+ * @param int $defaultLifetime The default lifetime
+ */
+ public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+ {
+ $this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/RedisTagAwareAdapter.php b/console/skel/symfony/cache/Adapter/RedisTagAwareAdapter.php
new file mode 100644
index 0000000..f936afd
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/RedisTagAwareAdapter.php
@@ -0,0 +1,292 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Predis\Connection\Aggregate\ClusterInterface;
+use Predis\Connection\Aggregate\PredisCluster;
+use Predis\Response\Status;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\DeflateMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Marshaller\TagAwareMarshaller;
+use Symfony\Component\Cache\Traits\RedisTrait;
+
+/**
+ * Stores tag id <> cache id relationship as a Redis Set, lookup on invalidation using RENAME+SMEMBERS.
+ *
+ * Set (tag relation info) is stored without expiry (non-volatile), while cache always gets an expiry (volatile) even
+ * if not set by caller. Thus if you configure redis with the right eviction policy you can be safe this tag <> cache
+ * relationship survives eviction (cache cleanup when Redis runs out of memory).
+ *
+ * Requirements:
+ * - Client: PHP Redis or Predis
+ * Note: Due to lack of RENAME support it is NOT recommended to use Cluster on Predis, instead use phpredis.
+ * - Server: Redis 2.8+
+ * Configured with any `volatile-*` eviction policy, OR `noeviction` if it will NEVER fill up memory
+ *
+ * Design limitations:
+ * - Max 4 billion cache keys per cache tag as limited by Redis Set datatype.
+ * E.g. If you use a "all" items tag for expiry instead of clear(), that limits you to 4 billion cache items also.
+ *
+ * @see https://redis.io/topics/lru-cache#eviction-policies Documentation for Redis eviction policies.
+ * @see https://redis.io/topics/data-types#sets Documentation for Redis Set datatype.
+ *
+ * @author Nicolas Grekas
+ * @author André Rømcke
+ */
+class RedisTagAwareAdapter extends AbstractTagAwareAdapter
+{
+ use RedisTrait;
+
+ /**
+ * Limits for how many keys are deleted in batch.
+ */
+ private const BULK_DELETE_LIMIT = 10000;
+
+ /**
+ * On cache items without a lifetime set, we set it to 100 days. This is to make sure cache items are
+ * preferred to be evicted over tag Sets, if eviction policy is configured according to requirements.
+ */
+ private const DEFAULT_CACHE_TTL = 8640000;
+
+ /**
+ * @var string|null detected eviction policy used on Redis server
+ */
+ private $redisEvictionPolicy;
+
+ /**
+ * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient The redis client
+ * @param string $namespace The default namespace
+ * @param int $defaultLifetime The default lifetime
+ */
+ public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+ {
+ if ($redisClient instanceof \Predis\ClientInterface && $redisClient->getConnection() instanceof ClusterInterface && !$redisClient->getConnection() instanceof PredisCluster) {
+ throw new InvalidArgumentException(sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, \get_class($redisClient->getConnection())));
+ }
+
+ if (\defined('Redis::OPT_COMPRESSION') && ($redisClient instanceof \Redis || $redisClient instanceof \RedisArray || $redisClient instanceof \RedisCluster)) {
+ $compression = $redisClient->getOption(\Redis::OPT_COMPRESSION);
+
+ foreach (\is_array($compression) ? $compression : [$compression] as $c) {
+ if (\Redis::COMPRESSION_NONE !== $c) {
+ throw new InvalidArgumentException(sprintf('phpredis compression must be disabled when using "%s", use "%s" instead.', static::class, DeflateMarshaller::class));
+ }
+ }
+ }
+
+ $this->init($redisClient, $namespace, $defaultLifetime, new TagAwareMarshaller($marshaller));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $delTagData = []): array
+ {
+ $eviction = $this->getRedisEvictionPolicy();
+ if ('noeviction' !== $eviction && 0 !== strpos($eviction, 'volatile-')) {
+ CacheItem::log($this->logger, sprintf('Redis maxmemory-policy setting "%s" is *not* supported by RedisTagAwareAdapter, use "noeviction" or "volatile-*" eviction policies', $eviction));
+
+ return false;
+ }
+
+ // serialize values
+ if (!$serialized = $this->marshaller->marshall($values, $failed)) {
+ return $failed;
+ }
+
+ // While pipeline isn't supported on RedisCluster, other setups will at least benefit from doing this in one op
+ $results = $this->pipeline(static function () use ($serialized, $lifetime, $addTagData, $delTagData, $failed) {
+ // Store cache items, force a ttl if none is set, as there is no MSETEX we need to set each one
+ foreach ($serialized as $id => $value) {
+ yield 'setEx' => [
+ $id,
+ 0 >= $lifetime ? self::DEFAULT_CACHE_TTL : $lifetime,
+ $value,
+ ];
+ }
+
+ // Add and Remove Tags
+ foreach ($addTagData as $tagId => $ids) {
+ if (!$failed || $ids = array_diff($ids, $failed)) {
+ yield 'sAdd' => array_merge([$tagId], $ids);
+ }
+ }
+
+ foreach ($delTagData as $tagId => $ids) {
+ if (!$failed || $ids = array_diff($ids, $failed)) {
+ yield 'sRem' => array_merge([$tagId], $ids);
+ }
+ }
+ });
+
+ foreach ($results as $id => $result) {
+ // Skip results of SADD/SREM operations, they'll be 1 or 0 depending on if set value already existed or not
+ if (is_numeric($result)) {
+ continue;
+ }
+ // setEx results
+ if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) {
+ $failed[] = $id;
+ }
+ }
+
+ return $failed;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDeleteYieldTags(array $ids): iterable
+ {
+ $lua = <<<'EOLUA'
+ local v = redis.call('GET', KEYS[1])
+ redis.call('DEL', KEYS[1])
+
+ if not v or v:len() <= 13 or v:byte(1) ~= 0x9D or v:byte(6) ~= 0 or v:byte(10) ~= 0x5F then
+ return ''
+ end
+
+ return v:sub(14, 13 + v:byte(13) + v:byte(12) * 256 + v:byte(11) * 65536)
+EOLUA;
+
+ if ($this->redis instanceof \Predis\ClientInterface) {
+ $evalArgs = [$lua, 1, &$id];
+ } else {
+ $evalArgs = [$lua, [&$id], 1];
+ }
+
+ $results = $this->pipeline(function () use ($ids, &$id, $evalArgs) {
+ foreach ($ids as $id) {
+ yield 'eval' => $evalArgs;
+ }
+ });
+
+ foreach ($results as $id => $result) {
+ try {
+ yield $id => !\is_string($result) || '' === $result ? [] : $this->marshaller->unmarshall($result);
+ } catch (\Exception $e) {
+ yield $id => [];
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDeleteTagRelations(array $tagData): bool
+ {
+ $this->pipeline(static function () use ($tagData) {
+ foreach ($tagData as $tagId => $idList) {
+ array_unshift($idList, $tagId);
+ yield 'sRem' => $idList;
+ }
+ })->rewind();
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doInvalidate(array $tagIds): bool
+ {
+ if (!$this->redis instanceof \Predis\ClientInterface || !$this->redis->getConnection() instanceof PredisCluster) {
+ $movedTagSetIds = $this->renameKeys($this->redis, $tagIds);
+ } else {
+ $clusterConnection = $this->redis->getConnection();
+ $tagIdsByConnection = new \SplObjectStorage();
+ $movedTagSetIds = [];
+
+ foreach ($tagIds as $id) {
+ $connection = $clusterConnection->getConnectionByKey($id);
+ $slot = $tagIdsByConnection[$connection] ?? $tagIdsByConnection[$connection] = new \ArrayObject();
+ $slot[] = $id;
+ }
+
+ foreach ($tagIdsByConnection as $connection) {
+ $slot = $tagIdsByConnection[$connection];
+ $movedTagSetIds = array_merge($movedTagSetIds, $this->renameKeys(new $this->redis($connection, $this->redis->getOptions()), $slot->getArrayCopy()));
+ }
+ }
+
+ // No Sets found
+ if (!$movedTagSetIds) {
+ return false;
+ }
+
+ // Now safely take the time to read the keys in each set and collect ids we need to delete
+ $tagIdSets = $this->pipeline(static function () use ($movedTagSetIds) {
+ foreach ($movedTagSetIds as $movedTagId) {
+ yield 'sMembers' => [$movedTagId];
+ }
+ });
+
+ // Return combination of the temporary Tag Set ids and their values (cache ids)
+ $ids = array_merge($movedTagSetIds, ...iterator_to_array($tagIdSets, false));
+
+ // Delete cache in chunks to avoid overloading the connection
+ foreach (array_chunk(array_unique($ids), self::BULK_DELETE_LIMIT) as $chunkIds) {
+ $this->doDelete($chunkIds);
+ }
+
+ return true;
+ }
+
+ /**
+ * Renames several keys in order to be able to operate on them without risk of race conditions.
+ *
+ * Filters out keys that do not exist before returning new keys.
+ *
+ * @see https://redis.io/commands/rename
+ * @see https://redis.io/topics/cluster-spec#keys-hash-tags
+ *
+ * @return array Filtered list of the valid moved keys (only those that existed)
+ */
+ private function renameKeys($redis, array $ids): array
+ {
+ $newIds = [];
+ $uniqueToken = bin2hex(random_bytes(10));
+
+ $results = $this->pipeline(static function () use ($ids, $uniqueToken) {
+ foreach ($ids as $id) {
+ yield 'rename' => [$id, '{'.$id.'}'.$uniqueToken];
+ }
+ }, $redis);
+
+ foreach ($results as $id => $result) {
+ if (true === $result || ($result instanceof Status && Status::get('OK') === $result)) {
+ // Only take into account if ok (key existed), will be false on phpredis if it did not exist
+ $newIds[] = '{'.$id.'}'.$uniqueToken;
+ }
+ }
+
+ return $newIds;
+ }
+
+ private function getRedisEvictionPolicy(): string
+ {
+ if (null !== $this->redisEvictionPolicy) {
+ return $this->redisEvictionPolicy;
+ }
+
+ foreach ($this->getHosts() as $host) {
+ $info = $host->info('Memory');
+ $info = isset($info['Memory']) ? $info['Memory'] : $info;
+
+ return $this->redisEvictionPolicy = $info['maxmemory_policy'];
+ }
+
+ return $this->redisEvictionPolicy = '';
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/SimpleCacheAdapter.php b/console/skel/symfony/cache/Adapter/SimpleCacheAdapter.php
new file mode 100644
index 0000000..d0d42e5
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/SimpleCacheAdapter.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+@trigger_error(sprintf('The "%s" class is @deprecated since Symfony 4.3, use "Psr16Adapter" instead.', SimpleCacheAdapter::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use Psr16Adapter instead.
+ */
+class SimpleCacheAdapter extends Psr16Adapter
+{
+}
diff --git a/console/skel/symfony/cache/Adapter/TagAwareAdapter.php b/console/skel/symfony/cache/Adapter/TagAwareAdapter.php
new file mode 100644
index 0000000..7c0ee30
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/TagAwareAdapter.php
@@ -0,0 +1,429 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\InvalidArgumentException;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Component\Cache\Traits\ProxyTrait;
+use Symfony\Contracts\Cache\TagAwareCacheInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface
+{
+ const TAGS_PREFIX = "\0tags\0";
+
+ use ProxyTrait;
+ use ContractsTrait;
+
+ private $deferred = [];
+ private $createCacheItem;
+ private $setCacheItemTags;
+ private $getTagsByKey;
+ private $invalidateTags;
+ private $tags;
+ private $knownTagVersions = [];
+ private $knownTagVersionsTtl;
+
+ public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, float $knownTagVersionsTtl = 0.15)
+ {
+ $this->pool = $itemsPool;
+ $this->tags = $tagsPool ?: $itemsPool;
+ $this->knownTagVersionsTtl = $knownTagVersionsTtl;
+ $this->createCacheItem = \Closure::bind(
+ static function ($key, $value, CacheItem $protoItem) {
+ $item = new CacheItem();
+ $item->key = $key;
+ $item->value = $value;
+ $item->defaultLifetime = $protoItem->defaultLifetime;
+ $item->expiry = $protoItem->expiry;
+ $item->poolHash = $protoItem->poolHash;
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ $this->setCacheItemTags = \Closure::bind(
+ static function (CacheItem $item, $key, array &$itemTags) {
+ $item->isTaggable = true;
+ if (!$item->isHit) {
+ return $item;
+ }
+ if (isset($itemTags[$key])) {
+ foreach ($itemTags[$key] as $tag => $version) {
+ $item->metadata[CacheItem::METADATA_TAGS][$tag] = $tag;
+ }
+ unset($itemTags[$key]);
+ } else {
+ $item->value = null;
+ $item->isHit = false;
+ }
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ $this->getTagsByKey = \Closure::bind(
+ static function ($deferred) {
+ $tagsByKey = [];
+ foreach ($deferred as $key => $item) {
+ $tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? [];
+ }
+
+ return $tagsByKey;
+ },
+ null,
+ CacheItem::class
+ );
+ $this->invalidateTags = \Closure::bind(
+ static function (AdapterInterface $tagsAdapter, array $tags) {
+ foreach ($tags as $v) {
+ $v->defaultLifetime = 0;
+ $v->expiry = null;
+ $tagsAdapter->saveDeferred($v);
+ }
+
+ return $tagsAdapter->commit();
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function invalidateTags(array $tags)
+ {
+ $ok = true;
+ $tagsByKey = [];
+ $invalidatedTags = [];
+ foreach ($tags as $tag) {
+ CacheItem::validateKey($tag);
+ $invalidatedTags[$tag] = 0;
+ }
+
+ if ($this->deferred) {
+ $items = $this->deferred;
+ foreach ($items as $key => $item) {
+ if (!$this->pool->saveDeferred($item)) {
+ unset($this->deferred[$key]);
+ $ok = false;
+ }
+ }
+
+ $f = $this->getTagsByKey;
+ $tagsByKey = $f($items);
+ $this->deferred = [];
+ }
+
+ $tagVersions = $this->getTagVersions($tagsByKey, $invalidatedTags);
+ $f = $this->createCacheItem;
+
+ foreach ($tagsByKey as $key => $tags) {
+ $this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key]));
+ }
+ $ok = $this->pool->commit() && $ok;
+
+ if ($invalidatedTags) {
+ $f = $this->invalidateTags;
+ $ok = $f($this->tags, $invalidatedTags) && $ok;
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ if ($this->deferred) {
+ $this->commit();
+ }
+ if (!$this->pool->hasItem($key)) {
+ return false;
+ }
+
+ $itemTags = $this->pool->getItem(static::TAGS_PREFIX.$key);
+
+ if (!$itemTags->isHit()) {
+ return false;
+ }
+
+ if (!$itemTags = $itemTags->get()) {
+ return true;
+ }
+
+ foreach ($this->getTagVersions([$itemTags]) as $tag => $version) {
+ if ($itemTags[$tag] !== $version && 1 !== $itemTags[$tag] - $version) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ foreach ($this->getItems([$key]) as $item) {
+ return $item;
+ }
+
+ return null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ if ($this->deferred) {
+ $this->commit();
+ }
+ $tagKeys = [];
+
+ foreach ($keys as $key) {
+ if ('' !== $key && \is_string($key)) {
+ $key = static::TAGS_PREFIX.$key;
+ $tagKeys[$key] = $key;
+ }
+ }
+
+ try {
+ $items = $this->pool->getItems($tagKeys + $keys);
+ } catch (InvalidArgumentException $e) {
+ $this->pool->getItems($keys); // Should throw an exception
+
+ throw $e;
+ }
+
+ return $this->generateItems($items, $tagKeys);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
+ */
+ public function clear(/*string $prefix = ''*/)
+ {
+ $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
+
+ if ('' !== $prefix) {
+ foreach ($this->deferred as $key => $item) {
+ if (0 === strpos($key, $prefix)) {
+ unset($this->deferred[$key]);
+ }
+ }
+ } else {
+ $this->deferred = [];
+ }
+
+ if ($this->pool instanceof AdapterInterface) {
+ return $this->pool->clear($prefix);
+ }
+
+ return $this->pool->clear();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItem($key)
+ {
+ return $this->deleteItems([$key]);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItems(array $keys)
+ {
+ foreach ($keys as $key) {
+ if ('' !== $key && \is_string($key)) {
+ $keys[] = static::TAGS_PREFIX.$key;
+ }
+ }
+
+ return $this->pool->deleteItems($keys);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ if (!$item instanceof CacheItem) {
+ return false;
+ }
+ $this->deferred[$item->getKey()] = $item;
+
+ return $this->commit();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ if (!$item instanceof CacheItem) {
+ return false;
+ }
+ $this->deferred[$item->getKey()] = $item;
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ return $this->invalidateTags([]);
+ }
+
+ public function __sleep()
+ {
+ throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+ }
+
+ public function __wakeup()
+ {
+ throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+ }
+
+ public function __destruct()
+ {
+ $this->commit();
+ }
+
+ private function generateItems(iterable $items, array $tagKeys)
+ {
+ $bufferedItems = $itemTags = [];
+ $f = $this->setCacheItemTags;
+
+ foreach ($items as $key => $item) {
+ if (!$tagKeys) {
+ yield $key => $f($item, static::TAGS_PREFIX.$key, $itemTags);
+ continue;
+ }
+ if (!isset($tagKeys[$key])) {
+ $bufferedItems[$key] = $item;
+ continue;
+ }
+
+ unset($tagKeys[$key]);
+
+ if ($item->isHit()) {
+ $itemTags[$key] = $item->get() ?: [];
+ }
+
+ if (!$tagKeys) {
+ $tagVersions = $this->getTagVersions($itemTags);
+
+ foreach ($itemTags as $key => $tags) {
+ foreach ($tags as $tag => $version) {
+ if ($tagVersions[$tag] !== $version && 1 !== $version - $tagVersions[$tag]) {
+ unset($itemTags[$key]);
+ continue 2;
+ }
+ }
+ }
+ $tagVersions = $tagKeys = null;
+
+ foreach ($bufferedItems as $key => $item) {
+ yield $key => $f($item, static::TAGS_PREFIX.$key, $itemTags);
+ }
+ $bufferedItems = null;
+ }
+ }
+ }
+
+ private function getTagVersions(array $tagsByKey, array &$invalidatedTags = [])
+ {
+ $tagVersions = $invalidatedTags;
+
+ foreach ($tagsByKey as $tags) {
+ $tagVersions += $tags;
+ }
+
+ if (!$tagVersions) {
+ return [];
+ }
+
+ if (!$fetchTagVersions = 1 !== \func_num_args()) {
+ foreach ($tagsByKey as $tags) {
+ foreach ($tags as $tag => $version) {
+ if ($tagVersions[$tag] > $version) {
+ $tagVersions[$tag] = $version;
+ }
+ }
+ }
+ }
+
+ $now = microtime(true);
+ $tags = [];
+ foreach ($tagVersions as $tag => $version) {
+ $tags[$tag.static::TAGS_PREFIX] = $tag;
+ if ($fetchTagVersions || !isset($this->knownTagVersions[$tag])) {
+ $fetchTagVersions = true;
+ continue;
+ }
+ $version -= $this->knownTagVersions[$tag][1];
+ if ((0 !== $version && 1 !== $version) || $now - $this->knownTagVersions[$tag][0] >= $this->knownTagVersionsTtl) {
+ // reuse previously fetched tag versions up to the ttl, unless we are storing items or a potential miss arises
+ $fetchTagVersions = true;
+ } else {
+ $this->knownTagVersions[$tag][1] += $version;
+ }
+ }
+
+ if (!$fetchTagVersions) {
+ return $tagVersions;
+ }
+
+ foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) {
+ $tagVersions[$tag = $tags[$tag]] = $version->get() ?: 0;
+ if (isset($invalidatedTags[$tag])) {
+ $invalidatedTags[$tag] = $version->set(++$tagVersions[$tag]);
+ }
+ $this->knownTagVersions[$tag] = [$now, $tagVersions[$tag]];
+ }
+
+ return $tagVersions;
+ }
+}
diff --git a/console/skel/symfony/cache/Adapter/TagAwareAdapterInterface.php b/console/skel/symfony/cache/Adapter/TagAwareAdapterInterface.php
new file mode 100644
index 0000000..340048c
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/TagAwareAdapterInterface.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\InvalidArgumentException;
+
+/**
+ * Interface for invalidating cached items using tags.
+ *
+ * @author Nicolas Grekas
+ */
+interface TagAwareAdapterInterface extends AdapterInterface
+{
+ /**
+ * Invalidates cached items using tags.
+ *
+ * @param string[] $tags An array of tags to invalidate
+ *
+ * @return bool True on success
+ *
+ * @throws InvalidArgumentException When $tags is not valid
+ */
+ public function invalidateTags(array $tags);
+}
diff --git a/console/skel/symfony/cache/Adapter/TraceableAdapter.php b/console/skel/symfony/cache/Adapter/TraceableAdapter.php
new file mode 100644
index 0000000..7e9dac8
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/TraceableAdapter.php
@@ -0,0 +1,302 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * An adapter that collects data about all cache calls.
+ *
+ * @author Aaron Scherer
+ * @author Tobias Nyholm
+ * @author Nicolas Grekas
+ */
+class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
+{
+ protected $pool;
+ private $calls = [];
+
+ public function __construct(AdapterInterface $pool)
+ {
+ $this->pool = $pool;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ if (!$this->pool instanceof CacheInterface) {
+ throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', \get_class($this->pool), CacheInterface::class));
+ }
+
+ $isHit = true;
+ $callback = function (CacheItem $item, bool &$save) use ($callback, &$isHit) {
+ $isHit = $item->isHit();
+
+ return $callback($item, $save);
+ };
+
+ $event = $this->start(__FUNCTION__);
+ try {
+ $value = $this->pool->get($key, $callback, $beta, $metadata);
+ $event->result[$key] = \is_object($value) ? \get_class($value) : \gettype($value);
+ } finally {
+ $event->end = microtime(true);
+ }
+ if ($isHit) {
+ ++$event->hits;
+ } else {
+ ++$event->misses;
+ }
+
+ return $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ $item = $this->pool->getItem($key);
+ } finally {
+ $event->end = microtime(true);
+ }
+ if ($event->result[$key] = $item->isHit()) {
+ ++$event->hits;
+ } else {
+ ++$event->misses;
+ }
+
+ return $item;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result[$key] = $this->pool->hasItem($key);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItem($key)
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result[$key] = $this->pool->deleteItem($key);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result[$item->getKey()] = $this->pool->save($item);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result[$item->getKey()] = $this->pool->saveDeferred($item);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ $result = $this->pool->getItems($keys);
+ } finally {
+ $event->end = microtime(true);
+ }
+ $f = function () use ($result, $event) {
+ $event->result = [];
+ foreach ($result as $key => $item) {
+ if ($event->result[$key] = $item->isHit()) {
+ ++$event->hits;
+ } else {
+ ++$event->misses;
+ }
+ yield $key => $item;
+ }
+ };
+
+ return $f();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
+ */
+ public function clear(/*string $prefix = ''*/)
+ {
+ $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
+ $event = $this->start(__FUNCTION__);
+ try {
+ if ($this->pool instanceof AdapterInterface) {
+ return $event->result = $this->pool->clear($prefix);
+ }
+
+ return $event->result = $this->pool->clear();
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItems(array $keys)
+ {
+ $event = $this->start(__FUNCTION__);
+ $event->result['keys'] = $keys;
+ try {
+ return $event->result['result'] = $this->pool->deleteItems($keys);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result = $this->pool->commit();
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prune()
+ {
+ if (!$this->pool instanceof PruneableInterface) {
+ return false;
+ }
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result = $this->pool->prune();
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ if (!$this->pool instanceof ResetInterface) {
+ return;
+ }
+ $event = $this->start(__FUNCTION__);
+ try {
+ $this->pool->reset();
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete(string $key): bool
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result[$key] = $this->pool->deleteItem($key);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ public function getCalls()
+ {
+ return $this->calls;
+ }
+
+ public function clearCalls()
+ {
+ $this->calls = [];
+ }
+
+ protected function start($name)
+ {
+ $this->calls[] = $event = new TraceableAdapterEvent();
+ $event->name = $name;
+ $event->start = microtime(true);
+
+ return $event;
+ }
+}
+
+class TraceableAdapterEvent
+{
+ public $name;
+ public $start;
+ public $end;
+ public $result;
+ public $hits = 0;
+ public $misses = 0;
+}
diff --git a/console/skel/symfony/cache/Adapter/TraceableTagAwareAdapter.php b/console/skel/symfony/cache/Adapter/TraceableTagAwareAdapter.php
new file mode 100644
index 0000000..69461b8
--- /dev/null
+++ b/console/skel/symfony/cache/Adapter/TraceableTagAwareAdapter.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Symfony\Contracts\Cache\TagAwareCacheInterface;
+
+/**
+ * @author Robin Chalas
+ */
+class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface
+{
+ public function __construct(TagAwareAdapterInterface $pool)
+ {
+ parent::__construct($pool);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function invalidateTags(array $tags)
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result = $this->pool->invalidateTags($tags);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/CHANGELOG.md b/console/skel/symfony/cache/CHANGELOG.md
new file mode 100644
index 0000000..435eaf3
--- /dev/null
+++ b/console/skel/symfony/cache/CHANGELOG.md
@@ -0,0 +1,73 @@
+CHANGELOG
+=========
+
+4.4.0
+-----
+
+ * added support for connecting to Redis Sentinel clusters
+ * added argument `$prefix` to `AdapterInterface::clear()`
+ * improved `RedisTagAwareAdapter` to support Redis server >= 2.8 and up to 4B items per tag
+ * added `TagAwareMarshaller` for optimized data storage when using `AbstractTagAwareAdapter`
+ * added `DeflateMarshaller` to compress serialized values
+ * removed support for phpredis 4 `compression`
+ * [BC BREAK] `RedisTagAwareAdapter` is not compatible with `RedisCluster` from `Predis` anymore, use `phpredis` instead
+ * Marked the `CacheDataCollector` class as `@final`.
+
+4.3.0
+-----
+
+ * removed `psr/simple-cache` dependency, run `composer require psr/simple-cache` if you need it
+ * deprecated all PSR-16 adapters, use `Psr16Cache` or `Symfony\Contracts\Cache\CacheInterface` implementations instead
+ * deprecated `SimpleCacheAdapter`, use `Psr16Adapter` instead
+
+4.2.0
+-----
+
+ * added support for connecting to Redis clusters via DSN
+ * added support for configuring multiple Memcached servers via DSN
+ * added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available
+ * implemented `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache
+ * added sub-second expiry accuracy for backends that support it
+ * added support for phpredis 4 `compression` and `tcp_keepalive` options
+ * added automatic table creation when using Doctrine DBAL with PDO-based backends
+ * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool
+ * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead
+ * deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods
+ * added `CacheCollectorPass` (originally in `FrameworkBundle`)
+ * added `CachePoolClearerPass` (originally in `FrameworkBundle`)
+ * added `CachePoolPass` (originally in `FrameworkBundle`)
+ * added `CachePoolPrunerPass` (originally in `FrameworkBundle`)
+
+3.4.0
+-----
+
+ * added using options from Memcached DSN
+ * added PruneableInterface so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning
+ * added prune logic to FilesystemTrait, PhpFilesTrait, PdoTrait, TagAwareAdapter and ChainTrait
+ * now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, PhpFilesCache, PdoAdapter, PdoCache, ChainAdapter, and
+ ChainCache implement PruneableInterface and support manual stale cache pruning
+
+3.3.0
+-----
+
+ * added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any
+ * added PSR-16 "Simple Cache" implementations for all existing PSR-6 adapters
+ * added Psr6Cache and SimpleCacheAdapter for bidirectional interoperability between PSR-6 and PSR-16
+ * added MemcachedAdapter (PSR-6) and MemcachedCache (PSR-16)
+ * added TraceableAdapter (PSR-6) and TraceableCache (PSR-16)
+
+3.2.0
+-----
+
+ * added TagAwareAdapter for tags-based invalidation
+ * added PdoAdapter with PDO and Doctrine DBAL support
+ * added PhpArrayAdapter and PhpFilesAdapter for OPcache-backed shared memory storage (PHP 7+ only)
+ * added NullAdapter
+
+3.1.0
+-----
+
+ * added the component with strict PSR-6 implementations
+ * added ApcuAdapter, ArrayAdapter, FilesystemAdapter and RedisAdapter
+ * added AbstractAdapter, ChainAdapter and ProxyAdapter
+ * added DoctrineAdapter and DoctrineProvider for bidirectional interoperability with Doctrine Cache
diff --git a/console/skel/symfony/cache/CacheItem.php b/console/skel/symfony/cache/CacheItem.php
new file mode 100644
index 0000000..3abd50e
--- /dev/null
+++ b/console/skel/symfony/cache/CacheItem.php
@@ -0,0 +1,202 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Exception\LogicException;
+use Symfony\Contracts\Cache\ItemInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+final class CacheItem implements ItemInterface
+{
+ private const METADATA_EXPIRY_OFFSET = 1527506807;
+
+ protected $key;
+ protected $value;
+ protected $isHit = false;
+ protected $expiry;
+ protected $defaultLifetime;
+ protected $metadata = [];
+ protected $newMetadata = [];
+ protected $innerItem;
+ protected $poolHash;
+ protected $isTaggable = false;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getKey(): string
+ {
+ return $this->key;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get()
+ {
+ return $this->value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isHit(): bool
+ {
+ return $this->isHit;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return $this
+ */
+ public function set($value): self
+ {
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return $this
+ */
+ public function expiresAt($expiration): self
+ {
+ if (null === $expiration) {
+ $this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null;
+ } elseif ($expiration instanceof \DateTimeInterface) {
+ $this->expiry = (float) $expiration->format('U.u');
+ } else {
+ throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given', \is_object($expiration) ? \get_class($expiration) : \gettype($expiration)));
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return $this
+ */
+ public function expiresAfter($time): self
+ {
+ if (null === $time) {
+ $this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null;
+ } elseif ($time instanceof \DateInterval) {
+ $this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u');
+ } elseif (\is_int($time)) {
+ $this->expiry = $time + microtime(true);
+ } else {
+ throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', \is_object($time) ? \get_class($time) : \gettype($time)));
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tag($tags): ItemInterface
+ {
+ if (!$this->isTaggable) {
+ throw new LogicException(sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key));
+ }
+ if (!is_iterable($tags)) {
+ $tags = [$tags];
+ }
+ foreach ($tags as $tag) {
+ if (!\is_string($tag)) {
+ throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given', \is_object($tag) ? \get_class($tag) : \gettype($tag)));
+ }
+ if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) {
+ continue;
+ }
+ if ('' === $tag) {
+ throw new InvalidArgumentException('Cache tag length must be greater than zero');
+ }
+ if (false !== strpbrk($tag, self::RESERVED_CHARACTERS)) {
+ throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters %s', $tag, self::RESERVED_CHARACTERS));
+ }
+ $this->newMetadata[self::METADATA_TAGS][$tag] = $tag;
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadata(): array
+ {
+ return $this->metadata;
+ }
+
+ /**
+ * Returns the list of tags bound to the value coming from the pool storage if any.
+ *
+ * @deprecated since Symfony 4.2, use the "getMetadata()" method instead.
+ */
+ public function getPreviousTags(): array
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "getMetadata()" method instead.', __METHOD__), E_USER_DEPRECATED);
+
+ return $this->metadata[self::METADATA_TAGS] ?? [];
+ }
+
+ /**
+ * Validates a cache key according to PSR-6.
+ *
+ * @param string $key The key to validate
+ *
+ * @throws InvalidArgumentException When $key is not valid
+ */
+ public static function validateKey($key): string
+ {
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given', \is_object($key) ? \get_class($key) : \gettype($key)));
+ }
+ if ('' === $key) {
+ throw new InvalidArgumentException('Cache key length must be greater than zero');
+ }
+ if (false !== strpbrk($key, self::RESERVED_CHARACTERS)) {
+ throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters %s', $key, self::RESERVED_CHARACTERS));
+ }
+
+ return $key;
+ }
+
+ /**
+ * Internal logging helper.
+ *
+ * @internal
+ */
+ public static function log(?LoggerInterface $logger, string $message, array $context = [])
+ {
+ if ($logger) {
+ $logger->warning($message, $context);
+ } else {
+ $replace = [];
+ foreach ($context as $k => $v) {
+ if (is_scalar($v)) {
+ $replace['{'.$k.'}'] = $v;
+ }
+ }
+ @trigger_error(strtr($message, $replace), E_USER_WARNING);
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/DataCollector/CacheDataCollector.php b/console/skel/symfony/cache/DataCollector/CacheDataCollector.php
new file mode 100644
index 0000000..ed86fca
--- /dev/null
+++ b/console/skel/symfony/cache/DataCollector/CacheDataCollector.php
@@ -0,0 +1,194 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\DataCollector;
+
+use Symfony\Component\Cache\Adapter\TraceableAdapter;
+use Symfony\Component\Cache\Adapter\TraceableAdapterEvent;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\DataCollector\DataCollector;
+use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
+
+/**
+ * @author Aaron Scherer
+ * @author Tobias Nyholm
+ *
+ * @final since Symfony 4.4
+ */
+class CacheDataCollector extends DataCollector implements LateDataCollectorInterface
+{
+ /**
+ * @var TraceableAdapter[]
+ */
+ private $instances = [];
+
+ /**
+ * @param string $name
+ */
+ public function addInstance($name, TraceableAdapter $instance)
+ {
+ $this->instances[$name] = $instance;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param \Throwable|null $exception
+ */
+ public function collect(Request $request, Response $response/*, \Throwable $exception = null*/)
+ {
+ $empty = ['calls' => [], 'config' => [], 'options' => [], 'statistics' => []];
+ $this->data = ['instances' => $empty, 'total' => $empty];
+ foreach ($this->instances as $name => $instance) {
+ $this->data['instances']['calls'][$name] = $instance->getCalls();
+ }
+
+ $this->data['instances']['statistics'] = $this->calculateStatistics();
+ $this->data['total']['statistics'] = $this->calculateTotalStatistics();
+ }
+
+ public function reset()
+ {
+ $this->data = [];
+ foreach ($this->instances as $instance) {
+ $instance->clearCalls();
+ }
+ }
+
+ public function lateCollect()
+ {
+ $this->data['instances']['calls'] = $this->cloneVar($this->data['instances']['calls']);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'cache';
+ }
+
+ /**
+ * Method returns amount of logged Cache reads: "get" calls.
+ *
+ * @return array
+ */
+ public function getStatistics()
+ {
+ return $this->data['instances']['statistics'];
+ }
+
+ /**
+ * Method returns the statistic totals.
+ *
+ * @return array
+ */
+ public function getTotals()
+ {
+ return $this->data['total']['statistics'];
+ }
+
+ /**
+ * Method returns all logged Cache call objects.
+ *
+ * @return mixed
+ */
+ public function getCalls()
+ {
+ return $this->data['instances']['calls'];
+ }
+
+ private function calculateStatistics(): array
+ {
+ $statistics = [];
+ foreach ($this->data['instances']['calls'] as $name => $calls) {
+ $statistics[$name] = [
+ 'calls' => 0,
+ 'time' => 0,
+ 'reads' => 0,
+ 'writes' => 0,
+ 'deletes' => 0,
+ 'hits' => 0,
+ 'misses' => 0,
+ ];
+ /** @var TraceableAdapterEvent $call */
+ foreach ($calls as $call) {
+ ++$statistics[$name]['calls'];
+ $statistics[$name]['time'] += $call->end - $call->start;
+ if ('get' === $call->name) {
+ ++$statistics[$name]['reads'];
+ if ($call->hits) {
+ ++$statistics[$name]['hits'];
+ } else {
+ ++$statistics[$name]['misses'];
+ ++$statistics[$name]['writes'];
+ }
+ } elseif ('getItem' === $call->name) {
+ ++$statistics[$name]['reads'];
+ if ($call->hits) {
+ ++$statistics[$name]['hits'];
+ } else {
+ ++$statistics[$name]['misses'];
+ }
+ } elseif ('getItems' === $call->name) {
+ $statistics[$name]['reads'] += $call->hits + $call->misses;
+ $statistics[$name]['hits'] += $call->hits;
+ $statistics[$name]['misses'] += $call->misses;
+ } elseif ('hasItem' === $call->name) {
+ ++$statistics[$name]['reads'];
+ if (false === $call->result) {
+ ++$statistics[$name]['misses'];
+ } else {
+ ++$statistics[$name]['hits'];
+ }
+ } elseif ('save' === $call->name) {
+ ++$statistics[$name]['writes'];
+ } elseif ('deleteItem' === $call->name) {
+ ++$statistics[$name]['deletes'];
+ }
+ }
+ if ($statistics[$name]['reads']) {
+ $statistics[$name]['hit_read_ratio'] = round(100 * $statistics[$name]['hits'] / $statistics[$name]['reads'], 2);
+ } else {
+ $statistics[$name]['hit_read_ratio'] = null;
+ }
+ }
+
+ return $statistics;
+ }
+
+ private function calculateTotalStatistics(): array
+ {
+ $statistics = $this->getStatistics();
+ $totals = [
+ 'calls' => 0,
+ 'time' => 0,
+ 'reads' => 0,
+ 'writes' => 0,
+ 'deletes' => 0,
+ 'hits' => 0,
+ 'misses' => 0,
+ ];
+ foreach ($statistics as $name => $values) {
+ foreach ($totals as $key => $value) {
+ $totals[$key] += $statistics[$name][$key];
+ }
+ }
+ if ($totals['reads']) {
+ $totals['hit_read_ratio'] = round(100 * $totals['hits'] / $totals['reads'], 2);
+ } else {
+ $totals['hit_read_ratio'] = null;
+ }
+
+ return $totals;
+ }
+}
diff --git a/console/skel/symfony/cache/DependencyInjection/CacheCollectorPass.php b/console/skel/symfony/cache/DependencyInjection/CacheCollectorPass.php
new file mode 100644
index 0000000..6193d34
--- /dev/null
+++ b/console/skel/symfony/cache/DependencyInjection/CacheCollectorPass.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\DependencyInjection;
+
+use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
+use Symfony\Component\Cache\Adapter\TraceableAdapter;
+use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Inject a data collector to all the cache services to be able to get detailed statistics.
+ *
+ * @author Tobias Nyholm
+ */
+class CacheCollectorPass implements CompilerPassInterface
+{
+ private $dataCollectorCacheId;
+ private $cachePoolTag;
+ private $cachePoolRecorderInnerSuffix;
+
+ public function __construct(string $dataCollectorCacheId = 'data_collector.cache', string $cachePoolTag = 'cache.pool', string $cachePoolRecorderInnerSuffix = '.recorder_inner')
+ {
+ $this->dataCollectorCacheId = $dataCollectorCacheId;
+ $this->cachePoolTag = $cachePoolTag;
+ $this->cachePoolRecorderInnerSuffix = $cachePoolRecorderInnerSuffix;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->dataCollectorCacheId)) {
+ return;
+ }
+
+ $collectorDefinition = $container->getDefinition($this->dataCollectorCacheId);
+ foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $attributes) {
+ $definition = $container->getDefinition($id);
+ if ($definition->isAbstract()) {
+ continue;
+ }
+
+ $recorder = new Definition(is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class);
+ $recorder->setTags($definition->getTags());
+ $recorder->setPublic($definition->isPublic());
+ $recorder->setArguments([new Reference($innerId = $id.$this->cachePoolRecorderInnerSuffix)]);
+
+ $definition->setTags([]);
+ $definition->setPublic(false);
+
+ $container->setDefinition($innerId, $definition);
+ $container->setDefinition($id, $recorder);
+
+ // Tell the collector to add the new instance
+ $collectorDefinition->addMethodCall('addInstance', [$id, new Reference($id)]);
+ $collectorDefinition->setPublic(false);
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/DependencyInjection/CachePoolClearerPass.php b/console/skel/symfony/cache/DependencyInjection/CachePoolClearerPass.php
new file mode 100644
index 0000000..3ca89a3
--- /dev/null
+++ b/console/skel/symfony/cache/DependencyInjection/CachePoolClearerPass.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * @author Nicolas Grekas
+ */
+class CachePoolClearerPass implements CompilerPassInterface
+{
+ private $cachePoolClearerTag;
+
+ public function __construct(string $cachePoolClearerTag = 'cache.pool.clearer')
+ {
+ $this->cachePoolClearerTag = $cachePoolClearerTag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ $container->getParameterBag()->remove('cache.prefix.seed');
+
+ foreach ($container->findTaggedServiceIds($this->cachePoolClearerTag) as $id => $attr) {
+ $clearer = $container->getDefinition($id);
+ $pools = [];
+ foreach ($clearer->getArgument(0) as $name => $ref) {
+ if ($container->hasDefinition($ref)) {
+ $pools[$name] = new Reference($ref);
+ }
+ }
+ $clearer->replaceArgument(0, $pools);
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/DependencyInjection/CachePoolPass.php b/console/skel/symfony/cache/DependencyInjection/CachePoolPass.php
new file mode 100644
index 0000000..f52d027
--- /dev/null
+++ b/console/skel/symfony/cache/DependencyInjection/CachePoolPass.php
@@ -0,0 +1,227 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\DependencyInjection;
+
+use Symfony\Component\Cache\Adapter\AbstractAdapter;
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\Cache\Adapter\ChainAdapter;
+use Symfony\Component\DependencyInjection\ChildDefinition;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * @author Nicolas Grekas
+ */
+class CachePoolPass implements CompilerPassInterface
+{
+ private $cachePoolTag;
+ private $kernelResetTag;
+ private $cacheClearerId;
+ private $cachePoolClearerTag;
+ private $cacheSystemClearerId;
+ private $cacheSystemClearerTag;
+
+ public function __construct(string $cachePoolTag = 'cache.pool', string $kernelResetTag = 'kernel.reset', string $cacheClearerId = 'cache.global_clearer', string $cachePoolClearerTag = 'cache.pool.clearer', string $cacheSystemClearerId = 'cache.system_clearer', string $cacheSystemClearerTag = 'kernel.cache_clearer')
+ {
+ $this->cachePoolTag = $cachePoolTag;
+ $this->kernelResetTag = $kernelResetTag;
+ $this->cacheClearerId = $cacheClearerId;
+ $this->cachePoolClearerTag = $cachePoolClearerTag;
+ $this->cacheSystemClearerId = $cacheSystemClearerId;
+ $this->cacheSystemClearerTag = $cacheSystemClearerTag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if ($container->hasParameter('cache.prefix.seed')) {
+ $seed = '.'.$container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed'));
+ } else {
+ $seed = '_'.$container->getParameter('kernel.project_dir');
+ }
+ $seed .= '.'.$container->getParameter('kernel.container_class');
+
+ $allPools = [];
+ $clearers = [];
+ $attributes = [
+ 'provider',
+ 'name',
+ 'namespace',
+ 'default_lifetime',
+ 'reset',
+ ];
+ foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) {
+ $adapter = $pool = $container->getDefinition($id);
+ if ($pool->isAbstract()) {
+ continue;
+ }
+ $class = $adapter->getClass();
+ while ($adapter instanceof ChildDefinition) {
+ $adapter = $container->findDefinition($adapter->getParent());
+ $class = $class ?: $adapter->getClass();
+ if ($t = $adapter->getTag($this->cachePoolTag)) {
+ $tags[0] += $t[0];
+ }
+ }
+ $name = $tags[0]['name'] ?? $id;
+ if (!isset($tags[0]['namespace'])) {
+ $namespaceSeed = $seed;
+ if (null !== $class) {
+ $namespaceSeed .= '.'.$class;
+ }
+
+ $tags[0]['namespace'] = $this->getNamespace($namespaceSeed, $name);
+ }
+ if (isset($tags[0]['clearer'])) {
+ $clearer = $tags[0]['clearer'];
+ while ($container->hasAlias($clearer)) {
+ $clearer = (string) $container->getAlias($clearer);
+ }
+ } else {
+ $clearer = null;
+ }
+ unset($tags[0]['clearer'], $tags[0]['name']);
+
+ if (isset($tags[0]['provider'])) {
+ $tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider']));
+ }
+
+ if (ChainAdapter::class === $class) {
+ $adapters = [];
+ foreach ($adapter->getArgument(0) as $provider => $adapter) {
+ if ($adapter instanceof ChildDefinition) {
+ $chainedPool = $adapter;
+ } else {
+ $chainedPool = $adapter = new ChildDefinition($adapter);
+ }
+
+ $chainedTags = [\is_int($provider) ? [] : ['provider' => $provider]];
+ $chainedClass = '';
+
+ while ($adapter instanceof ChildDefinition) {
+ $adapter = $container->findDefinition($adapter->getParent());
+ $chainedClass = $chainedClass ?: $adapter->getClass();
+ if ($t = $adapter->getTag($this->cachePoolTag)) {
+ $chainedTags[0] += $t[0];
+ }
+ }
+
+ if (ChainAdapter::class === $chainedClass) {
+ throw new InvalidArgumentException(sprintf('Invalid service "%s": chain of adapters cannot reference another chain, found "%s".', $id, $chainedPool->getParent()));
+ }
+
+ $i = 0;
+
+ if (isset($chainedTags[0]['provider'])) {
+ $chainedPool->replaceArgument($i++, new Reference(static::getServiceProvider($container, $chainedTags[0]['provider'])));
+ }
+
+ if (isset($tags[0]['namespace']) && ArrayAdapter::class !== $adapter->getClass()) {
+ $chainedPool->replaceArgument($i++, $tags[0]['namespace']);
+ }
+
+ if (isset($tags[0]['default_lifetime'])) {
+ $chainedPool->replaceArgument($i++, $tags[0]['default_lifetime']);
+ }
+
+ $adapters[] = $chainedPool;
+ }
+
+ $pool->replaceArgument(0, $adapters);
+ unset($tags[0]['provider'], $tags[0]['namespace']);
+ $i = 1;
+ } else {
+ $i = 0;
+ }
+
+ foreach ($attributes as $attr) {
+ if (!isset($tags[0][$attr])) {
+ // no-op
+ } elseif ('reset' === $attr) {
+ if ($tags[0][$attr]) {
+ $pool->addTag($this->kernelResetTag, ['method' => $tags[0][$attr]]);
+ }
+ } elseif ('namespace' !== $attr || ArrayAdapter::class !== $class) {
+ $pool->replaceArgument($i++, $tags[0][$attr]);
+ }
+ unset($tags[0][$attr]);
+ }
+ if (!empty($tags[0])) {
+ throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime" and "reset", found "%s".', $this->cachePoolTag, $id, implode('", "', array_keys($tags[0]))));
+ }
+
+ if (null !== $clearer) {
+ $clearers[$clearer][$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
+ }
+
+ $allPools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
+ }
+
+ $notAliasedCacheClearerId = $this->cacheClearerId;
+ while ($container->hasAlias($this->cacheClearerId)) {
+ $this->cacheClearerId = (string) $container->getAlias($this->cacheClearerId);
+ }
+ if ($container->hasDefinition($this->cacheClearerId)) {
+ $clearers[$notAliasedCacheClearerId] = $allPools;
+ }
+
+ foreach ($clearers as $id => $pools) {
+ $clearer = $container->getDefinition($id);
+ if ($clearer instanceof ChildDefinition) {
+ $clearer->replaceArgument(0, $pools);
+ } else {
+ $clearer->setArgument(0, $pools);
+ }
+ $clearer->addTag($this->cachePoolClearerTag);
+
+ if ($this->cacheSystemClearerId === $id) {
+ $clearer->addTag($this->cacheSystemClearerTag);
+ }
+ }
+
+ if ($container->hasDefinition('console.command.cache_pool_list')) {
+ $container->getDefinition('console.command.cache_pool_list')->replaceArgument(0, array_keys($allPools));
+ }
+ }
+
+ private function getNamespace(string $seed, string $id)
+ {
+ return substr(str_replace('/', '-', base64_encode(hash('sha256', $id.$seed, true))), 0, 10);
+ }
+
+ /**
+ * @internal
+ */
+ public static function getServiceProvider(ContainerBuilder $container, $name)
+ {
+ $container->resolveEnvPlaceholders($name, null, $usedEnvs);
+
+ if ($usedEnvs || preg_match('#^[a-z]++:#', $name)) {
+ $dsn = $name;
+
+ if (!$container->hasDefinition($name = '.cache_connection.'.ContainerBuilder::hash($dsn))) {
+ $definition = new Definition(AbstractAdapter::class);
+ $definition->setPublic(false);
+ $definition->setFactory([AbstractAdapter::class, 'createConnection']);
+ $definition->setArguments([$dsn, ['lazy' => true]]);
+ $container->setDefinition($name, $definition);
+ }
+ }
+
+ return $name;
+ }
+}
diff --git a/console/skel/symfony/cache/DependencyInjection/CachePoolPrunerPass.php b/console/skel/symfony/cache/DependencyInjection/CachePoolPrunerPass.php
new file mode 100644
index 0000000..e569962
--- /dev/null
+++ b/console/skel/symfony/cache/DependencyInjection/CachePoolPrunerPass.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\DependencyInjection;
+
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * @author Rob Frawley 2nd
+ */
+class CachePoolPrunerPass implements CompilerPassInterface
+{
+ private $cacheCommandServiceId;
+ private $cachePoolTag;
+
+ public function __construct(string $cacheCommandServiceId = 'console.command.cache_pool_prune', string $cachePoolTag = 'cache.pool')
+ {
+ $this->cacheCommandServiceId = $cacheCommandServiceId;
+ $this->cachePoolTag = $cachePoolTag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->cacheCommandServiceId)) {
+ return;
+ }
+
+ $services = [];
+
+ foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) {
+ $class = $container->getParameterBag()->resolveValue($container->getDefinition($id)->getClass());
+
+ if (!$reflection = $container->getReflectionClass($class)) {
+ throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
+ }
+
+ if ($reflection->implementsInterface(PruneableInterface::class)) {
+ $services[$id] = new Reference($id);
+ }
+ }
+
+ $container->getDefinition($this->cacheCommandServiceId)->replaceArgument(0, new IteratorArgument($services));
+ }
+}
diff --git a/console/skel/symfony/cache/DoctrineProvider.php b/console/skel/symfony/cache/DoctrineProvider.php
new file mode 100644
index 0000000..d7e0bca
--- /dev/null
+++ b/console/skel/symfony/cache/DoctrineProvider.php
@@ -0,0 +1,114 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache;
+
+use Doctrine\Common\Cache\CacheProvider;
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+class DoctrineProvider extends CacheProvider implements PruneableInterface, ResettableInterface
+{
+ private $pool;
+
+ public function __construct(CacheItemPoolInterface $pool)
+ {
+ $this->pool = $pool;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prune()
+ {
+ return $this->pool instanceof PruneableInterface && $this->pool->prune();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ if ($this->pool instanceof ResetInterface) {
+ $this->pool->reset();
+ }
+ $this->setNamespace($this->getNamespace());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch($id)
+ {
+ $item = $this->pool->getItem(rawurlencode($id));
+
+ return $item->isHit() ? $item->get() : false;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ protected function doContains($id)
+ {
+ return $this->pool->hasItem(rawurlencode($id));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ protected function doSave($id, $data, $lifeTime = 0)
+ {
+ $item = $this->pool->getItem(rawurlencode($id));
+
+ if (0 < $lifeTime) {
+ $item->expiresAfter($lifeTime);
+ }
+
+ return $this->pool->save($item->set($data));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ protected function doDelete($id)
+ {
+ return $this->pool->deleteItem(rawurlencode($id));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ protected function doFlush()
+ {
+ return $this->pool->clear();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return array|null
+ */
+ protected function doGetStats()
+ {
+ return null;
+ }
+}
diff --git a/console/skel/symfony/cache/Exception/CacheException.php b/console/skel/symfony/cache/Exception/CacheException.php
new file mode 100644
index 0000000..d2e975b
--- /dev/null
+++ b/console/skel/symfony/cache/Exception/CacheException.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Exception;
+
+use Psr\Cache\CacheException as Psr6CacheInterface;
+use Psr\SimpleCache\CacheException as SimpleCacheInterface;
+
+if (interface_exists(SimpleCacheInterface::class)) {
+ class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface
+ {
+ }
+} else {
+ class CacheException extends \Exception implements Psr6CacheInterface
+ {
+ }
+}
diff --git a/console/skel/symfony/cache/Exception/InvalidArgumentException.php b/console/skel/symfony/cache/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..7f9584a
--- /dev/null
+++ b/console/skel/symfony/cache/Exception/InvalidArgumentException.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Exception;
+
+use Psr\Cache\InvalidArgumentException as Psr6CacheInterface;
+use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface;
+
+if (interface_exists(SimpleCacheInterface::class)) {
+ class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface
+ {
+ }
+} else {
+ class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface
+ {
+ }
+}
diff --git a/console/skel/symfony/cache/Exception/LogicException.php b/console/skel/symfony/cache/Exception/LogicException.php
new file mode 100644
index 0000000..9ffa7ed
--- /dev/null
+++ b/console/skel/symfony/cache/Exception/LogicException.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Exception;
+
+use Psr\Cache\CacheException as Psr6CacheInterface;
+use Psr\SimpleCache\CacheException as SimpleCacheInterface;
+
+if (interface_exists(SimpleCacheInterface::class)) {
+ class LogicException extends \LogicException implements Psr6CacheInterface, SimpleCacheInterface
+ {
+ }
+} else {
+ class LogicException extends \LogicException implements Psr6CacheInterface
+ {
+ }
+}
diff --git a/console/skel/symfony/cache/LICENSE b/console/skel/symfony/cache/LICENSE
new file mode 100644
index 0000000..a7ec708
--- /dev/null
+++ b/console/skel/symfony/cache/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2016-2020 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/console/skel/symfony/cache/LockRegistry.php b/console/skel/symfony/cache/LockRegistry.php
new file mode 100644
index 0000000..80601be
--- /dev/null
+++ b/console/skel/symfony/cache/LockRegistry.php
@@ -0,0 +1,154 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Cache\ItemInterface;
+
+/**
+ * LockRegistry is used internally by existing adapters to protect against cache stampede.
+ *
+ * It does so by wrapping the computation of items in a pool of locks.
+ * Foreach each apps, there can be at most 20 concurrent processes that
+ * compute items at the same time and only one per cache-key.
+ *
+ * @author Nicolas Grekas
+ */
+final class LockRegistry
+{
+ private static $openedFiles = [];
+ private static $lockedFiles = [];
+
+ /**
+ * The number of items in this list controls the max number of concurrent processes.
+ */
+ private static $files = [
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractTagAwareAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AdapterInterface.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ApcuAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ArrayAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ChainAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemTagAwareAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'MemcachedAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'NullAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PdoAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpArrayAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpFilesAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ProxyAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'Psr16Adapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisTagAwareAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'SimpleCacheAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapterInterface.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableTagAwareAdapter.php',
+ ];
+
+ /**
+ * Defines a set of existing files that will be used as keys to acquire locks.
+ *
+ * @return array The previously defined set of files
+ */
+ public static function setFiles(array $files): array
+ {
+ $previousFiles = self::$files;
+ self::$files = $files;
+
+ foreach (self::$openedFiles as $file) {
+ if ($file) {
+ flock($file, LOCK_UN);
+ fclose($file);
+ }
+ }
+ self::$openedFiles = self::$lockedFiles = [];
+
+ return $previousFiles;
+ }
+
+ public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null)
+ {
+ $key = self::$files ? crc32($item->getKey()) % \count(self::$files) : -1;
+
+ if ($key < 0 || (self::$lockedFiles[$key] ?? false) || !$lock = self::open($key)) {
+ return $callback($item, $save);
+ }
+
+ while (true) {
+ try {
+ // race to get the lock in non-blocking mode
+ $locked = flock($lock, LOCK_EX | LOCK_NB, $wouldBlock);
+
+ if ($locked || !$wouldBlock) {
+ $logger && $logger->info(sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]);
+ self::$lockedFiles[$key] = true;
+
+ $value = $callback($item, $save);
+
+ if ($save) {
+ if ($setMetadata) {
+ $setMetadata($item);
+ }
+
+ $pool->save($item->set($value));
+ $save = false;
+ }
+
+ return $value;
+ }
+ // if we failed the race, retry locking in blocking mode to wait for the winner
+ $logger && $logger->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]);
+ flock($lock, LOCK_SH);
+ } finally {
+ flock($lock, LOCK_UN);
+ unset(self::$lockedFiles[$key]);
+ }
+ static $signalingException, $signalingCallback;
+ $signalingException = $signalingException ?? unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}");
+ $signalingCallback = $signalingCallback ?? function () use ($signalingException) { throw $signalingException; };
+
+ try {
+ $value = $pool->get($item->getKey(), $signalingCallback, 0);
+ $logger && $logger->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]);
+ $save = false;
+
+ return $value;
+ } catch (\Exception $e) {
+ if ($signalingException !== $e) {
+ throw $e;
+ }
+ $logger && $logger->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]);
+ }
+ }
+
+ return null;
+ }
+
+ private static function open(int $key)
+ {
+ if (null !== $h = self::$openedFiles[$key] ?? null) {
+ return $h;
+ }
+ set_error_handler(function () {});
+ try {
+ $h = fopen(self::$files[$key], 'r+');
+ } finally {
+ restore_error_handler();
+ }
+
+ return self::$openedFiles[$key] = $h ?: @fopen(self::$files[$key], 'r');
+ }
+}
diff --git a/console/skel/symfony/cache/Marshaller/DefaultMarshaller.php b/console/skel/symfony/cache/Marshaller/DefaultMarshaller.php
new file mode 100644
index 0000000..f4cad03
--- /dev/null
+++ b/console/skel/symfony/cache/Marshaller/DefaultMarshaller.php
@@ -0,0 +1,99 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Marshaller;
+
+use Symfony\Component\Cache\Exception\CacheException;
+
+/**
+ * Serializes/unserializes values using igbinary_serialize() if available, serialize() otherwise.
+ *
+ * @author Nicolas Grekas
+ */
+class DefaultMarshaller implements MarshallerInterface
+{
+ private $useIgbinarySerialize = true;
+
+ public function __construct(bool $useIgbinarySerialize = null)
+ {
+ if (null === $useIgbinarySerialize) {
+ $useIgbinarySerialize = \extension_loaded('igbinary') && (\PHP_VERSION_ID < 70400 || version_compare('3.1.0', phpversion('igbinary'), '<='));
+ } elseif ($useIgbinarySerialize && (!\extension_loaded('igbinary') || (\PHP_VERSION_ID >= 70400 && version_compare('3.1.0', phpversion('igbinary'), '>')))) {
+ throw new CacheException(\extension_loaded('igbinary') && \PHP_VERSION_ID >= 70400 ? 'Please upgrade the "igbinary" PHP extension to v3.1 or higher.' : 'The "igbinary" PHP extension is not loaded.');
+ }
+ $this->useIgbinarySerialize = $useIgbinarySerialize;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function marshall(array $values, ?array &$failed): array
+ {
+ $serialized = $failed = [];
+
+ foreach ($values as $id => $value) {
+ try {
+ if ($this->useIgbinarySerialize) {
+ $serialized[$id] = igbinary_serialize($value);
+ } else {
+ $serialized[$id] = serialize($value);
+ }
+ } catch (\Exception $e) {
+ $failed[] = $id;
+ }
+ }
+
+ return $serialized;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unmarshall(string $value)
+ {
+ if ('b:0;' === $value) {
+ return false;
+ }
+ if ('N;' === $value) {
+ return null;
+ }
+ static $igbinaryNull;
+ if ($value === ($igbinaryNull ?? $igbinaryNull = \extension_loaded('igbinary') && (\PHP_VERSION_ID < 70400 || version_compare('3.1.0', phpversion('igbinary'), '<=')) ? igbinary_serialize(null) : false)) {
+ return null;
+ }
+ $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
+ try {
+ if (':' === ($value[1] ?? ':')) {
+ if (false !== $value = unserialize($value)) {
+ return $value;
+ }
+ } elseif (false === $igbinaryNull) {
+ throw new \RuntimeException('Failed to unserialize values, did you forget to install the "igbinary" extension?');
+ } elseif (null !== $value = igbinary_unserialize($value)) {
+ return $value;
+ }
+
+ throw new \DomainException(error_get_last() ? error_get_last()['message'] : 'Failed to unserialize values.');
+ } catch (\Error $e) {
+ throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
+ } finally {
+ ini_set('unserialize_callback_func', $unserializeCallbackHandler);
+ }
+ }
+
+ /**
+ * @internal
+ */
+ public static function handleUnserializeCallback($class)
+ {
+ throw new \DomainException('Class not found: '.$class);
+ }
+}
diff --git a/console/skel/symfony/cache/Marshaller/DeflateMarshaller.php b/console/skel/symfony/cache/Marshaller/DeflateMarshaller.php
new file mode 100644
index 0000000..5544806
--- /dev/null
+++ b/console/skel/symfony/cache/Marshaller/DeflateMarshaller.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Marshaller;
+
+use Symfony\Component\Cache\Exception\CacheException;
+
+/**
+ * Compresses values using gzdeflate().
+ *
+ * @author Nicolas Grekas
+ */
+class DeflateMarshaller implements MarshallerInterface
+{
+ private $marshaller;
+
+ public function __construct(MarshallerInterface $marshaller)
+ {
+ if (!\function_exists('gzdeflate')) {
+ throw new CacheException('The "zlib" PHP extension is not loaded.');
+ }
+
+ $this->marshaller = $marshaller;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function marshall(array $values, ?array &$failed): array
+ {
+ return array_map('gzdeflate', $this->marshaller->marshall($values, $failed));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unmarshall(string $value)
+ {
+ if (false !== $inflatedValue = @gzinflate($value)) {
+ $value = $inflatedValue;
+ }
+
+ return $this->marshaller->unmarshall($value);
+ }
+}
diff --git a/console/skel/symfony/cache/Marshaller/MarshallerInterface.php b/console/skel/symfony/cache/Marshaller/MarshallerInterface.php
new file mode 100644
index 0000000..cdd6c40
--- /dev/null
+++ b/console/skel/symfony/cache/Marshaller/MarshallerInterface.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Marshaller;
+
+/**
+ * Serializes/unserializes PHP values.
+ *
+ * Implementations of this interface MUST deal with errors carefully. They MUST
+ * also deal with forward and backward compatibility at the storage format level.
+ *
+ * @author Nicolas Grekas
+ */
+interface MarshallerInterface
+{
+ /**
+ * Serializes a list of values.
+ *
+ * When serialization fails for a specific value, no exception should be
+ * thrown. Instead, its key should be listed in $failed.
+ */
+ public function marshall(array $values, ?array &$failed): array;
+
+ /**
+ * Unserializes a single value and throws an exception if anything goes wrong.
+ *
+ * @return mixed
+ *
+ * @throws \Exception Whenever unserialization fails
+ */
+ public function unmarshall(string $value);
+}
diff --git a/console/skel/symfony/cache/Marshaller/TagAwareMarshaller.php b/console/skel/symfony/cache/Marshaller/TagAwareMarshaller.php
new file mode 100644
index 0000000..5d1e303
--- /dev/null
+++ b/console/skel/symfony/cache/Marshaller/TagAwareMarshaller.php
@@ -0,0 +1,89 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Marshaller;
+
+/**
+ * A marshaller optimized for data structures generated by AbstractTagAwareAdapter.
+ *
+ * @author Nicolas Grekas
+ */
+class TagAwareMarshaller implements MarshallerInterface
+{
+ private $marshaller;
+
+ public function __construct(MarshallerInterface $marshaller = null)
+ {
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function marshall(array $values, ?array &$failed): array
+ {
+ $failed = $notSerialized = $serialized = [];
+
+ foreach ($values as $id => $value) {
+ if (\is_array($value) && \is_array($value['tags'] ?? null) && \array_key_exists('value', $value) && \count($value) === 2 + (\is_string($value['meta'] ?? null) && 8 === \strlen($value['meta']))) {
+ // if the value is an array with keys "tags", "value" and "meta", use a compact serialization format
+ // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F allow detecting this format quickly in unmarshall()
+
+ $v = $this->marshaller->marshall($value, $f);
+
+ if ($f) {
+ $f = [];
+ $failed[] = $id;
+ } else {
+ if ([] === $value['tags']) {
+ $v['tags'] = '';
+ }
+
+ $serialized[$id] = "\x9D".($value['meta'] ?? "\0\0\0\0\0\0\0\0").pack('N', \strlen($v['tags'])).$v['tags'].$v['value'];
+ $serialized[$id][9] = "\x5F";
+ }
+ } else {
+ // other arbitratry values are serialized using the decorated marshaller below
+ $notSerialized[$id] = $value;
+ }
+ }
+
+ if ($notSerialized) {
+ $serialized += $this->marshaller->marshall($notSerialized, $f);
+ $failed = array_merge($failed, $f);
+ }
+
+ return $serialized;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unmarshall(string $value)
+ {
+ // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
+ if (13 >= \strlen($value) || "\x9D" !== $value[0] || "\0" !== $value[5] || "\x5F" !== $value[9]) {
+ return $this->marshaller->unmarshall($value);
+ }
+
+ // data consists of value, tags and metadata which we need to unpack
+ $meta = substr($value, 1, 12);
+ $meta[8] = "\0";
+ $tagLen = unpack('Nlen', $meta, 8)['len'];
+ $meta = substr($meta, 0, 8);
+
+ return [
+ 'value' => $this->marshaller->unmarshall(substr($value, 13 + $tagLen)),
+ 'tags' => $tagLen ? $this->marshaller->unmarshall(substr($value, 13, $tagLen)) : [],
+ 'meta' => "\0\0\0\0\0\0\0\0" === $meta ? null : $meta,
+ ];
+ }
+}
diff --git a/console/skel/symfony/cache/PruneableInterface.php b/console/skel/symfony/cache/PruneableInterface.php
new file mode 100644
index 0000000..4261525
--- /dev/null
+++ b/console/skel/symfony/cache/PruneableInterface.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache;
+
+/**
+ * Interface extends psr-6 and psr-16 caches to allow for pruning (deletion) of all expired cache items.
+ */
+interface PruneableInterface
+{
+ /**
+ * @return bool
+ */
+ public function prune();
+}
diff --git a/console/skel/symfony/cache/Psr16Cache.php b/console/skel/symfony/cache/Psr16Cache.php
new file mode 100644
index 0000000..6501516
--- /dev/null
+++ b/console/skel/symfony/cache/Psr16Cache.php
@@ -0,0 +1,277 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache;
+
+use Psr\Cache\CacheException as Psr6CacheException;
+use Psr\Cache\CacheItemPoolInterface;
+use Psr\SimpleCache\CacheException as SimpleCacheException;
+use Psr\SimpleCache\CacheInterface;
+use Symfony\Component\Cache\Adapter\AdapterInterface;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Traits\ProxyTrait;
+
+/**
+ * Turns a PSR-6 cache into a PSR-16 one.
+ *
+ * @author Nicolas Grekas
+ */
+class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterface
+{
+ use ProxyTrait;
+
+ private const METADATA_EXPIRY_OFFSET = 1527506807;
+
+ private $createCacheItem;
+ private $cacheItemPrototype;
+
+ public function __construct(CacheItemPoolInterface $pool)
+ {
+ $this->pool = $pool;
+
+ if (!$pool instanceof AdapterInterface) {
+ return;
+ }
+ $cacheItemPrototype = &$this->cacheItemPrototype;
+ $createCacheItem = \Closure::bind(
+ static function ($key, $value, $allowInt = false) use (&$cacheItemPrototype) {
+ $item = clone $cacheItemPrototype;
+ $item->key = $allowInt && \is_int($key) ? (string) $key : CacheItem::validateKey($key);
+ $item->value = $value;
+ $item->isHit = false;
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ $this->createCacheItem = function ($key, $value, $allowInt = false) use ($createCacheItem) {
+ if (null === $this->cacheItemPrototype) {
+ $this->get($allowInt && \is_int($key) ? (string) $key : $key);
+ }
+ $this->createCacheItem = $createCacheItem;
+
+ return $createCacheItem($key, null, $allowInt)->set($value);
+ };
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($key, $default = null)
+ {
+ try {
+ $item = $this->pool->getItem($key);
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ if (null === $this->cacheItemPrototype) {
+ $this->cacheItemPrototype = clone $item;
+ $this->cacheItemPrototype->set(null);
+ }
+
+ return $item->isHit() ? $item->get() : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function set($key, $value, $ttl = null)
+ {
+ try {
+ if (null !== $f = $this->createCacheItem) {
+ $item = $f($key, $value);
+ } else {
+ $item = $this->pool->getItem($key)->set($value);
+ }
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ if (null !== $ttl) {
+ $item->expiresAfter($ttl);
+ }
+
+ return $this->pool->save($item);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function delete($key)
+ {
+ try {
+ return $this->pool->deleteItem($key);
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function clear()
+ {
+ return $this->pool->clear();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return iterable
+ */
+ public function getMultiple($keys, $default = null)
+ {
+ if ($keys instanceof \Traversable) {
+ $keys = iterator_to_array($keys, false);
+ } elseif (!\is_array($keys)) {
+ throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+ }
+
+ try {
+ $items = $this->pool->getItems($keys);
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ $values = [];
+
+ if (!$this->pool instanceof AdapterInterface) {
+ foreach ($items as $key => $item) {
+ $values[$key] = $item->isHit() ? $item->get() : $default;
+ }
+
+ return $values;
+ }
+
+ foreach ($items as $key => $item) {
+ if (!$item->isHit()) {
+ $values[$key] = $default;
+ continue;
+ }
+ $values[$key] = $item->get();
+
+ if (!$metadata = $item->getMetadata()) {
+ continue;
+ }
+ unset($metadata[CacheItem::METADATA_TAGS]);
+
+ if ($metadata) {
+ $values[$key] = ["\x9D".pack('VN', (int) (0.1 + $metadata[CacheItem::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[CacheItem::METADATA_CTIME])."\x5F" => $values[$key]];
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null)
+ {
+ $valuesIsArray = \is_array($values);
+ if (!$valuesIsArray && !$values instanceof \Traversable) {
+ throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
+ }
+ $items = [];
+
+ try {
+ if (null !== $f = $this->createCacheItem) {
+ $valuesIsArray = false;
+ foreach ($values as $key => $value) {
+ $items[$key] = $f($key, $value, true);
+ }
+ } elseif ($valuesIsArray) {
+ $items = [];
+ foreach ($values as $key => $value) {
+ $items[] = (string) $key;
+ }
+ $items = $this->pool->getItems($items);
+ } else {
+ foreach ($values as $key => $value) {
+ if (\is_int($key)) {
+ $key = (string) $key;
+ }
+ $items[$key] = $this->pool->getItem($key)->set($value);
+ }
+ }
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ $ok = true;
+
+ foreach ($items as $key => $item) {
+ if ($valuesIsArray) {
+ $item->set($values[$key]);
+ }
+ if (null !== $ttl) {
+ $item->expiresAfter($ttl);
+ }
+ $ok = $this->pool->saveDeferred($item) && $ok;
+ }
+
+ return $this->pool->commit() && $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteMultiple($keys)
+ {
+ if ($keys instanceof \Traversable) {
+ $keys = iterator_to_array($keys, false);
+ } elseif (!\is_array($keys)) {
+ throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+ }
+
+ try {
+ return $this->pool->deleteItems($keys);
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function has($key)
+ {
+ try {
+ return $this->pool->hasItem($key);
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/README.md b/console/skel/symfony/cache/README.md
new file mode 100644
index 0000000..c4ab752
--- /dev/null
+++ b/console/skel/symfony/cache/README.md
@@ -0,0 +1,18 @@
+Symfony PSR-6 implementation for caching
+========================================
+
+This component provides an extended [PSR-6](http://www.php-fig.org/psr/psr-6/)
+implementation for adding cache to your applications. It is designed to have a
+low overhead so that caching is fastest. It ships with a few caching adapters
+for the most widespread and suited to caching backends. It also provides a
+`doctrine/cache` proxy adapter to cover more advanced caching needs and a proxy
+adapter for greater interoperability between PSR-6 implementations.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/cache.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/console/skel/symfony/cache/ResettableInterface.php b/console/skel/symfony/cache/ResettableInterface.php
new file mode 100644
index 0000000..7b0a853
--- /dev/null
+++ b/console/skel/symfony/cache/ResettableInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache;
+
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * Resets a pool's local state.
+ */
+interface ResettableInterface extends ResetInterface
+{
+}
diff --git a/console/skel/symfony/cache/Simple/AbstractCache.php b/console/skel/symfony/cache/Simple/AbstractCache.php
new file mode 100644
index 0000000..b3477e9
--- /dev/null
+++ b/console/skel/symfony/cache/Simple/AbstractCache.php
@@ -0,0 +1,199 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Simple;
+
+use Psr\Log\LoggerAwareInterface;
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\AbstractAdapter;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\AbstractTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', AbstractCache::class, AbstractAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use AbstractAdapter and type-hint for CacheInterface instead.
+ */
+abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface
+{
+ /**
+ * @internal
+ */
+ protected const NS_SEPARATOR = ':';
+
+ use AbstractTrait {
+ deleteItems as private;
+ AbstractTrait::deleteItem as delete;
+ AbstractTrait::hasItem as has;
+ }
+
+ private $defaultLifetime;
+
+ protected function __construct(string $namespace = '', int $defaultLifetime = 0)
+ {
+ $this->defaultLifetime = max(0, $defaultLifetime);
+ $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
+ if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
+ throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($key, $default = null)
+ {
+ $id = $this->getId($key);
+
+ try {
+ foreach ($this->doFetch([$id]) as $value) {
+ return $value;
+ }
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
+ }
+
+ return $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function set($key, $value, $ttl = null)
+ {
+ CacheItem::validateKey($key);
+
+ return $this->setMultiple([$key => $value], $ttl);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return iterable
+ */
+ public function getMultiple($keys, $default = null)
+ {
+ if ($keys instanceof \Traversable) {
+ $keys = iterator_to_array($keys, false);
+ } elseif (!\is_array($keys)) {
+ throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+ }
+ $ids = [];
+
+ foreach ($keys as $key) {
+ $ids[] = $this->getId($key);
+ }
+ try {
+ $values = $this->doFetch($ids);
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e]);
+ $values = [];
+ }
+ $ids = array_combine($ids, $keys);
+
+ return $this->generateValues($values, $ids, $default);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null)
+ {
+ if (!\is_array($values) && !$values instanceof \Traversable) {
+ throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
+ }
+ $valuesById = [];
+
+ foreach ($values as $key => $value) {
+ if (\is_int($key)) {
+ $key = (string) $key;
+ }
+ $valuesById[$this->getId($key)] = $value;
+ }
+ if (false === $ttl = $this->normalizeTtl($ttl)) {
+ return $this->doDelete(array_keys($valuesById));
+ }
+
+ try {
+ $e = $this->doSave($valuesById, $ttl);
+ } catch (\Exception $e) {
+ }
+ if (true === $e || [] === $e) {
+ return true;
+ }
+ $keys = [];
+ foreach (\is_array($e) ? $e : array_keys($valuesById) as $id) {
+ $keys[] = substr($id, \strlen($this->namespace));
+ }
+ $message = 'Failed to save values'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['keys' => $keys, 'exception' => $e instanceof \Exception ? $e : null]);
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteMultiple($keys)
+ {
+ if ($keys instanceof \Traversable) {
+ $keys = iterator_to_array($keys, false);
+ } elseif (!\is_array($keys)) {
+ throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+ }
+
+ return $this->deleteItems($keys);
+ }
+
+ private function normalizeTtl($ttl)
+ {
+ if (null === $ttl) {
+ return $this->defaultLifetime;
+ }
+ if ($ttl instanceof \DateInterval) {
+ $ttl = (int) \DateTime::createFromFormat('U', 0)->add($ttl)->format('U');
+ }
+ if (\is_int($ttl)) {
+ return 0 < $ttl ? $ttl : false;
+ }
+
+ throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)));
+ }
+
+ private function generateValues(iterable $values, array &$keys, $default): iterable
+ {
+ try {
+ foreach ($values as $id => $value) {
+ if (!isset($keys[$id])) {
+ $id = key($keys);
+ }
+ $key = $keys[$id];
+ unset($keys[$id]);
+ yield $key => $value;
+ }
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e]);
+ }
+
+ foreach ($keys as $key) {
+ yield $key => $default;
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/Simple/ApcuCache.php b/console/skel/symfony/cache/Simple/ApcuCache.php
new file mode 100644
index 0000000..f1eb946
--- /dev/null
+++ b/console/skel/symfony/cache/Simple/ApcuCache.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Simple;
+
+use Symfony\Component\Cache\Adapter\ApcuAdapter;
+use Symfony\Component\Cache\Traits\ApcuTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ApcuCache::class, ApcuAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use ApcuAdapter and type-hint for CacheInterface instead.
+ */
+class ApcuCache extends AbstractCache
+{
+ use ApcuTrait;
+
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null)
+ {
+ $this->init($namespace, $defaultLifetime, $version);
+ }
+}
diff --git a/console/skel/symfony/cache/Simple/ArrayCache.php b/console/skel/symfony/cache/Simple/ArrayCache.php
new file mode 100644
index 0000000..7174825
--- /dev/null
+++ b/console/skel/symfony/cache/Simple/ArrayCache.php
@@ -0,0 +1,167 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Simple;
+
+use Psr\Log\LoggerAwareInterface;
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ArrayTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ArrayCache::class, ArrayAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use ArrayAdapter and type-hint for CacheInterface instead.
+ */
+class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface
+{
+ use ArrayTrait {
+ ArrayTrait::deleteItem as delete;
+ ArrayTrait::hasItem as has;
+ }
+
+ private $defaultLifetime;
+
+ /**
+ * @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
+ */
+ public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true)
+ {
+ $this->defaultLifetime = $defaultLifetime;
+ $this->storeSerialized = $storeSerialized;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($key, $default = null)
+ {
+ if (!\is_string($key) || !isset($this->expiries[$key])) {
+ CacheItem::validateKey($key);
+ }
+ if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > microtime(true) || !$this->delete($key))) {
+ $this->values[$key] = null;
+
+ return $default;
+ }
+ if (!$this->storeSerialized) {
+ return $this->values[$key];
+ }
+ $value = $this->unfreeze($key, $isHit);
+
+ return $isHit ? $value : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return iterable
+ */
+ public function getMultiple($keys, $default = null)
+ {
+ if ($keys instanceof \Traversable) {
+ $keys = iterator_to_array($keys, false);
+ } elseif (!\is_array($keys)) {
+ throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+ }
+ foreach ($keys as $key) {
+ if (!\is_string($key) || !isset($this->expiries[$key])) {
+ CacheItem::validateKey($key);
+ }
+ }
+
+ return $this->generateItems($keys, microtime(true), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; });
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteMultiple($keys)
+ {
+ if (!\is_array($keys) && !$keys instanceof \Traversable) {
+ throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+ }
+ foreach ($keys as $key) {
+ $this->delete($key);
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function set($key, $value, $ttl = null)
+ {
+ if (!\is_string($key)) {
+ CacheItem::validateKey($key);
+ }
+
+ return $this->setMultiple([$key => $value], $ttl);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null)
+ {
+ if (!\is_array($values) && !$values instanceof \Traversable) {
+ throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
+ }
+ $valuesArray = [];
+
+ foreach ($values as $key => $value) {
+ if (!\is_int($key) && !(\is_string($key) && isset($this->expiries[$key]))) {
+ CacheItem::validateKey($key);
+ }
+ $valuesArray[$key] = $value;
+ }
+ if (false === $ttl = $this->normalizeTtl($ttl)) {
+ return $this->deleteMultiple(array_keys($valuesArray));
+ }
+ $expiry = 0 < $ttl ? microtime(true) + $ttl : PHP_INT_MAX;
+
+ foreach ($valuesArray as $key => $value) {
+ if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
+ return false;
+ }
+ $this->values[$key] = $value;
+ $this->expiries[$key] = $expiry;
+ }
+
+ return true;
+ }
+
+ private function normalizeTtl($ttl)
+ {
+ if (null === $ttl) {
+ return $this->defaultLifetime;
+ }
+ if ($ttl instanceof \DateInterval) {
+ $ttl = (int) \DateTime::createFromFormat('U', 0)->add($ttl)->format('U');
+ }
+ if (\is_int($ttl)) {
+ return 0 < $ttl ? $ttl : false;
+ }
+
+ throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)));
+ }
+}
diff --git a/console/skel/symfony/cache/Simple/ChainCache.php b/console/skel/symfony/cache/Simple/ChainCache.php
new file mode 100644
index 0000000..57a169f
--- /dev/null
+++ b/console/skel/symfony/cache/Simple/ChainCache.php
@@ -0,0 +1,271 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Simple;
+
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\ChainAdapter;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ChainCache::class, ChainAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * Chains several caches together.
+ *
+ * Cached items are fetched from the first cache having them in its data store.
+ * They are saved and deleted in all caches at once.
+ *
+ * @deprecated since Symfony 4.3, use ChainAdapter and type-hint for CacheInterface instead.
+ */
+class ChainCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
+{
+ private $miss;
+ private $caches = [];
+ private $defaultLifetime;
+ private $cacheCount;
+
+ /**
+ * @param Psr16CacheInterface[] $caches The ordered list of caches used to fetch cached items
+ * @param int $defaultLifetime The lifetime of items propagated from lower caches to upper ones
+ */
+ public function __construct(array $caches, int $defaultLifetime = 0)
+ {
+ if (!$caches) {
+ throw new InvalidArgumentException('At least one cache must be specified.');
+ }
+
+ foreach ($caches as $cache) {
+ if (!$cache instanceof Psr16CacheInterface) {
+ throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($cache), Psr16CacheInterface::class));
+ }
+ }
+
+ $this->miss = new \stdClass();
+ $this->caches = array_values($caches);
+ $this->cacheCount = \count($this->caches);
+ $this->defaultLifetime = 0 < $defaultLifetime ? $defaultLifetime : null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($key, $default = null)
+ {
+ $miss = null !== $default && \is_object($default) ? $default : $this->miss;
+
+ foreach ($this->caches as $i => $cache) {
+ $value = $cache->get($key, $miss);
+
+ if ($miss !== $value) {
+ while (0 <= --$i) {
+ $this->caches[$i]->set($key, $value, $this->defaultLifetime);
+ }
+
+ return $value;
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return iterable
+ */
+ public function getMultiple($keys, $default = null)
+ {
+ $miss = null !== $default && \is_object($default) ? $default : $this->miss;
+
+ return $this->generateItems($this->caches[0]->getMultiple($keys, $miss), 0, $miss, $default);
+ }
+
+ private function generateItems(iterable $values, int $cacheIndex, $miss, $default): iterable
+ {
+ $missing = [];
+ $nextCacheIndex = $cacheIndex + 1;
+ $nextCache = isset($this->caches[$nextCacheIndex]) ? $this->caches[$nextCacheIndex] : null;
+
+ foreach ($values as $k => $value) {
+ if ($miss !== $value) {
+ yield $k => $value;
+ } elseif (!$nextCache) {
+ yield $k => $default;
+ } else {
+ $missing[] = $k;
+ }
+ }
+
+ if ($missing) {
+ $cache = $this->caches[$cacheIndex];
+ $values = $this->generateItems($nextCache->getMultiple($missing, $miss), $nextCacheIndex, $miss, $default);
+
+ foreach ($values as $k => $value) {
+ if ($miss !== $value) {
+ $cache->set($k, $value, $this->defaultLifetime);
+ yield $k => $value;
+ } else {
+ yield $k => $default;
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function has($key)
+ {
+ foreach ($this->caches as $cache) {
+ if ($cache->has($key)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function clear()
+ {
+ $cleared = true;
+ $i = $this->cacheCount;
+
+ while ($i--) {
+ $cleared = $this->caches[$i]->clear() && $cleared;
+ }
+
+ return $cleared;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function delete($key)
+ {
+ $deleted = true;
+ $i = $this->cacheCount;
+
+ while ($i--) {
+ $deleted = $this->caches[$i]->delete($key) && $deleted;
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteMultiple($keys)
+ {
+ if ($keys instanceof \Traversable) {
+ $keys = iterator_to_array($keys, false);
+ }
+ $deleted = true;
+ $i = $this->cacheCount;
+
+ while ($i--) {
+ $deleted = $this->caches[$i]->deleteMultiple($keys) && $deleted;
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function set($key, $value, $ttl = null)
+ {
+ $saved = true;
+ $i = $this->cacheCount;
+
+ while ($i--) {
+ $saved = $this->caches[$i]->set($key, $value, $ttl) && $saved;
+ }
+
+ return $saved;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null)
+ {
+ if ($values instanceof \Traversable) {
+ $valuesIterator = $values;
+ $values = function () use ($valuesIterator, &$values) {
+ $generatedValues = [];
+
+ foreach ($valuesIterator as $key => $value) {
+ yield $key => $value;
+ $generatedValues[$key] = $value;
+ }
+
+ $values = $generatedValues;
+ };
+ $values = $values();
+ }
+ $saved = true;
+ $i = $this->cacheCount;
+
+ while ($i--) {
+ $saved = $this->caches[$i]->setMultiple($values, $ttl) && $saved;
+ }
+
+ return $saved;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prune()
+ {
+ $pruned = true;
+
+ foreach ($this->caches as $cache) {
+ if ($cache instanceof PruneableInterface) {
+ $pruned = $cache->prune() && $pruned;
+ }
+ }
+
+ return $pruned;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ foreach ($this->caches as $cache) {
+ if ($cache instanceof ResetInterface) {
+ $cache->reset();
+ }
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/Simple/DoctrineCache.php b/console/skel/symfony/cache/Simple/DoctrineCache.php
new file mode 100644
index 0000000..6a6d003
--- /dev/null
+++ b/console/skel/symfony/cache/Simple/DoctrineCache.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Simple;
+
+use Doctrine\Common\Cache\CacheProvider;
+use Symfony\Component\Cache\Adapter\DoctrineAdapter;
+use Symfony\Component\Cache\Traits\DoctrineTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', DoctrineCache::class, DoctrineAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use DoctrineAdapter and type-hint for CacheInterface instead.
+ */
+class DoctrineCache extends AbstractCache
+{
+ use DoctrineTrait;
+
+ public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0)
+ {
+ parent::__construct('', $defaultLifetime);
+ $this->provider = $provider;
+ $provider->setNamespace($namespace);
+ }
+}
diff --git a/console/skel/symfony/cache/Simple/FilesystemCache.php b/console/skel/symfony/cache/Simple/FilesystemCache.php
new file mode 100644
index 0000000..8891abd
--- /dev/null
+++ b/console/skel/symfony/cache/Simple/FilesystemCache.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Simple;
+
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\FilesystemTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', FilesystemCache::class, FilesystemAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use FilesystemAdapter and type-hint for CacheInterface instead.
+ */
+class FilesystemCache extends AbstractCache implements PruneableInterface
+{
+ use FilesystemTrait;
+
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
+ {
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+ parent::__construct('', $defaultLifetime);
+ $this->init($namespace, $directory);
+ }
+}
diff --git a/console/skel/symfony/cache/Simple/MemcachedCache.php b/console/skel/symfony/cache/Simple/MemcachedCache.php
new file mode 100644
index 0000000..e193411
--- /dev/null
+++ b/console/skel/symfony/cache/Simple/MemcachedCache.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Simple;
+
+use Symfony\Component\Cache\Adapter\MemcachedAdapter;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Traits\MemcachedTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', MemcachedCache::class, MemcachedAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use MemcachedAdapter and type-hint for CacheInterface instead.
+ */
+class MemcachedCache extends AbstractCache
+{
+ use MemcachedTrait;
+
+ protected $maxIdLength = 250;
+
+ public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+ {
+ $this->init($client, $namespace, $defaultLifetime, $marshaller);
+ }
+}
diff --git a/console/skel/symfony/cache/Simple/NullCache.php b/console/skel/symfony/cache/Simple/NullCache.php
new file mode 100644
index 0000000..35600fc
--- /dev/null
+++ b/console/skel/symfony/cache/Simple/NullCache.php
@@ -0,0 +1,104 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Simple;
+
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\NullAdapter;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', NullCache::class, NullAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use NullAdapter and type-hint for CacheInterface instead.
+ */
+class NullCache implements Psr16CacheInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function get($key, $default = null)
+ {
+ return $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return iterable
+ */
+ public function getMultiple($keys, $default = null)
+ {
+ foreach ($keys as $key) {
+ yield $key => $default;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function has($key)
+ {
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function clear()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function delete($key)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteMultiple($keys)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function set($key, $value, $ttl = null)
+ {
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null)
+ {
+ return false;
+ }
+}
diff --git a/console/skel/symfony/cache/Simple/PdoCache.php b/console/skel/symfony/cache/Simple/PdoCache.php
new file mode 100644
index 0000000..07849e9
--- /dev/null
+++ b/console/skel/symfony/cache/Simple/PdoCache.php
@@ -0,0 +1,59 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Simple;
+
+use Symfony\Component\Cache\Adapter\PdoAdapter;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\PdoTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PdoCache::class, PdoAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use PdoAdapter and type-hint for CacheInterface instead.
+ */
+class PdoCache extends AbstractCache implements PruneableInterface
+{
+ use PdoTrait;
+
+ protected $maxIdLength = 255;
+
+ /**
+ * You can either pass an existing database connection as PDO instance or
+ * a Doctrine DBAL Connection or a DSN string that will be used to
+ * lazy-connect to the database when the cache is actually used.
+ *
+ * When a Doctrine DBAL Connection is passed, the cache table is created
+ * automatically when possible. Otherwise, use the createTable() method.
+ *
+ * List of available options:
+ * * db_table: The name of the table [default: cache_items]
+ * * db_id_col: The column where to store the cache id [default: item_id]
+ * * db_data_col: The column where to store the cache data [default: item_data]
+ * * db_lifetime_col: The column where to store the lifetime [default: item_lifetime]
+ * * db_time_col: The column where to store the timestamp [default: item_time]
+ * * db_username: The username when lazy-connect [default: '']
+ * * db_password: The password when lazy-connect [default: '']
+ * * db_connection_options: An array of driver-specific connection options [default: []]
+ *
+ * @param \PDO|Connection|string $connOrDsn a \PDO or Connection instance or DSN string or null
+ *
+ * @throws InvalidArgumentException When first argument is not PDO nor Connection nor string
+ * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
+ * @throws InvalidArgumentException When namespace contains invalid characters
+ */
+ public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
+ {
+ $this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
+ }
+}
diff --git a/console/skel/symfony/cache/Simple/PhpArrayCache.php b/console/skel/symfony/cache/Simple/PhpArrayCache.php
new file mode 100644
index 0000000..3711f46
--- /dev/null
+++ b/console/skel/symfony/cache/Simple/PhpArrayCache.php
@@ -0,0 +1,256 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Simple;
+
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\PhpArrayTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PhpArrayCache::class, PhpArrayAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use PhpArrayAdapter and type-hint for CacheInterface instead.
+ */
+class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
+{
+ use PhpArrayTrait;
+
+ /**
+ * @param string $file The PHP file were values are cached
+ * @param Psr16CacheInterface $fallbackPool A pool to fallback on when an item is not hit
+ */
+ public function __construct(string $file, Psr16CacheInterface $fallbackPool)
+ {
+ $this->file = $file;
+ $this->pool = $fallbackPool;
+ }
+
+ /**
+ * This adapter takes advantage of how PHP stores arrays in its latest versions.
+ *
+ * @param string $file The PHP file were values are cached
+ * @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit
+ *
+ * @return Psr16CacheInterface
+ */
+ public static function create($file, Psr16CacheInterface $fallbackPool)
+ {
+ return new static($file, $fallbackPool);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($key, $default = null)
+ {
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ }
+ if (null === $this->values) {
+ $this->initialize();
+ }
+ if (!isset($this->keys[$key])) {
+ return $this->pool->get($key, $default);
+ }
+ $value = $this->values[$this->keys[$key]];
+
+ if ('N;' === $value) {
+ return null;
+ }
+ if ($value instanceof \Closure) {
+ try {
+ return $value();
+ } catch (\Throwable $e) {
+ return $default;
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return iterable
+ */
+ public function getMultiple($keys, $default = null)
+ {
+ if ($keys instanceof \Traversable) {
+ $keys = iterator_to_array($keys, false);
+ } elseif (!\is_array($keys)) {
+ throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+ }
+ foreach ($keys as $key) {
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ }
+ }
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ return $this->generateItems($keys, $default);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function has($key)
+ {
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ }
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ return isset($this->keys[$key]) || $this->pool->has($key);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function delete($key)
+ {
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ }
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ return !isset($this->keys[$key]) && $this->pool->delete($key);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteMultiple($keys)
+ {
+ if (!\is_array($keys) && !$keys instanceof \Traversable) {
+ throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+ }
+
+ $deleted = true;
+ $fallbackKeys = [];
+
+ foreach ($keys as $key) {
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ }
+
+ if (isset($this->keys[$key])) {
+ $deleted = false;
+ } else {
+ $fallbackKeys[] = $key;
+ }
+ }
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ if ($fallbackKeys) {
+ $deleted = $this->pool->deleteMultiple($fallbackKeys) && $deleted;
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function set($key, $value, $ttl = null)
+ {
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ }
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ return !isset($this->keys[$key]) && $this->pool->set($key, $value, $ttl);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null)
+ {
+ if (!\is_array($values) && !$values instanceof \Traversable) {
+ throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
+ }
+
+ $saved = true;
+ $fallbackValues = [];
+
+ foreach ($values as $key => $value) {
+ if (!\is_string($key) && !\is_int($key)) {
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ }
+
+ if (isset($this->keys[$key])) {
+ $saved = false;
+ } else {
+ $fallbackValues[$key] = $value;
+ }
+ }
+
+ if ($fallbackValues) {
+ $saved = $this->pool->setMultiple($fallbackValues, $ttl) && $saved;
+ }
+
+ return $saved;
+ }
+
+ private function generateItems(array $keys, $default): iterable
+ {
+ $fallbackKeys = [];
+
+ foreach ($keys as $key) {
+ if (isset($this->keys[$key])) {
+ $value = $this->values[$this->keys[$key]];
+
+ if ('N;' === $value) {
+ yield $key => null;
+ } elseif ($value instanceof \Closure) {
+ try {
+ yield $key => $value();
+ } catch (\Throwable $e) {
+ yield $key => $default;
+ }
+ } else {
+ yield $key => $value;
+ }
+ } else {
+ $fallbackKeys[] = $key;
+ }
+ }
+
+ if ($fallbackKeys) {
+ yield from $this->pool->getMultiple($fallbackKeys, $default);
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/Simple/PhpFilesCache.php b/console/skel/symfony/cache/Simple/PhpFilesCache.php
new file mode 100644
index 0000000..060244a
--- /dev/null
+++ b/console/skel/symfony/cache/Simple/PhpFilesCache.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Simple;
+
+use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
+use Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\PhpFilesTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PhpFilesCache::class, PhpFilesAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use PhpFilesAdapter and type-hint for CacheInterface instead.
+ */
+class PhpFilesCache extends AbstractCache implements PruneableInterface
+{
+ use PhpFilesTrait;
+
+ /**
+ * @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire.
+ * Doing so is encouraged because it fits perfectly OPcache's memory model.
+ *
+ * @throws CacheException if OPcache is not enabled
+ */
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
+ {
+ $this->appendOnly = $appendOnly;
+ self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
+ parent::__construct('', $defaultLifetime);
+ $this->init($namespace, $directory);
+ $this->includeHandler = static function ($type, $msg, $file, $line) {
+ throw new \ErrorException($msg, 0, $type, $file, $line);
+ };
+ }
+}
diff --git a/console/skel/symfony/cache/Simple/Psr6Cache.php b/console/skel/symfony/cache/Simple/Psr6Cache.php
new file mode 100644
index 0000000..090f48c
--- /dev/null
+++ b/console/skel/symfony/cache/Simple/Psr6Cache.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Simple;
+
+use Symfony\Component\Cache\Psr16Cache;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" instead.', Psr6Cache::class, Psr16Cache::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use Psr16Cache instead.
+ */
+class Psr6Cache extends Psr16Cache
+{
+}
diff --git a/console/skel/symfony/cache/Simple/RedisCache.php b/console/skel/symfony/cache/Simple/RedisCache.php
new file mode 100644
index 0000000..9655a75
--- /dev/null
+++ b/console/skel/symfony/cache/Simple/RedisCache.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Simple;
+
+use Symfony\Component\Cache\Adapter\RedisAdapter;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Traits\RedisTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', RedisCache::class, RedisAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use RedisAdapter and type-hint for CacheInterface instead.
+ */
+class RedisCache extends AbstractCache
+{
+ use RedisTrait;
+
+ /**
+ * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient
+ */
+ public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+ {
+ $this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
+ }
+}
diff --git a/console/skel/symfony/cache/Simple/TraceableCache.php b/console/skel/symfony/cache/Simple/TraceableCache.php
new file mode 100644
index 0000000..2214f1c
--- /dev/null
+++ b/console/skel/symfony/cache/Simple/TraceableCache.php
@@ -0,0 +1,258 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Simple;
+
+use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
+use Symfony\Component\Cache\Adapter\TraceableAdapter;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', TraceableCache::class, TraceableAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
+
+/**
+ * @deprecated since Symfony 4.3, use TraceableAdapter and type-hint for CacheInterface instead.
+ */
+class TraceableCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
+{
+ private $pool;
+ private $miss;
+ private $calls = [];
+
+ public function __construct(Psr16CacheInterface $pool)
+ {
+ $this->pool = $pool;
+ $this->miss = new \stdClass();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($key, $default = null)
+ {
+ $miss = null !== $default && \is_object($default) ? $default : $this->miss;
+ $event = $this->start(__FUNCTION__);
+ try {
+ $value = $this->pool->get($key, $miss);
+ } finally {
+ $event->end = microtime(true);
+ }
+ if ($event->result[$key] = $miss !== $value) {
+ ++$event->hits;
+ } else {
+ ++$event->misses;
+ $value = $default;
+ }
+
+ return $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function has($key)
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result[$key] = $this->pool->has($key);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function delete($key)
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result[$key] = $this->pool->delete($key);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function set($key, $value, $ttl = null)
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result[$key] = $this->pool->set($key, $value, $ttl);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null)
+ {
+ $event = $this->start(__FUNCTION__);
+ $event->result['keys'] = [];
+
+ if ($values instanceof \Traversable) {
+ $values = function () use ($values, $event) {
+ foreach ($values as $k => $v) {
+ $event->result['keys'][] = $k;
+ yield $k => $v;
+ }
+ };
+ $values = $values();
+ } elseif (\is_array($values)) {
+ $event->result['keys'] = array_keys($values);
+ }
+
+ try {
+ return $event->result['result'] = $this->pool->setMultiple($values, $ttl);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return iterable
+ */
+ public function getMultiple($keys, $default = null)
+ {
+ $miss = null !== $default && \is_object($default) ? $default : $this->miss;
+ $event = $this->start(__FUNCTION__);
+ try {
+ $result = $this->pool->getMultiple($keys, $miss);
+ } finally {
+ $event->end = microtime(true);
+ }
+ $f = function () use ($result, $event, $miss, $default) {
+ $event->result = [];
+ foreach ($result as $key => $value) {
+ if ($event->result[$key] = $miss !== $value) {
+ ++$event->hits;
+ } else {
+ ++$event->misses;
+ $value = $default;
+ }
+ yield $key => $value;
+ }
+ };
+
+ return $f();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function clear()
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result = $this->pool->clear();
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteMultiple($keys)
+ {
+ $event = $this->start(__FUNCTION__);
+ if ($keys instanceof \Traversable) {
+ $keys = $event->result['keys'] = iterator_to_array($keys, false);
+ } else {
+ $event->result['keys'] = $keys;
+ }
+ try {
+ return $event->result['result'] = $this->pool->deleteMultiple($keys);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prune()
+ {
+ if (!$this->pool instanceof PruneableInterface) {
+ return false;
+ }
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result = $this->pool->prune();
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ if (!$this->pool instanceof ResetInterface) {
+ return;
+ }
+ $event = $this->start(__FUNCTION__);
+ try {
+ $this->pool->reset();
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ public function getCalls()
+ {
+ try {
+ return $this->calls;
+ } finally {
+ $this->calls = [];
+ }
+ }
+
+ private function start(string $name): TraceableCacheEvent
+ {
+ $this->calls[] = $event = new TraceableCacheEvent();
+ $event->name = $name;
+ $event->start = microtime(true);
+
+ return $event;
+ }
+}
+
+class TraceableCacheEvent
+{
+ public $name;
+ public $start;
+ public $end;
+ public $result;
+ public $hits = 0;
+ public $misses = 0;
+}
diff --git a/console/skel/symfony/cache/Traits/AbstractAdapterTrait.php b/console/skel/symfony/cache/Traits/AbstractAdapterTrait.php
new file mode 100644
index 0000000..8bf9354
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/AbstractAdapterTrait.php
@@ -0,0 +1,155 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+use Psr\Cache\CacheItemInterface;
+use Symfony\Component\Cache\CacheItem;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait AbstractAdapterTrait
+{
+ use AbstractTrait;
+
+ /**
+ * @var \Closure needs to be set by class, signature is function(string , mixed , bool )
+ */
+ private $createCacheItem;
+
+ /**
+ * @var \Closure needs to be set by class, signature is function(array , string , array <&expiredIds>)
+ */
+ private $mergeByLifetime;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ if ($this->deferred) {
+ $this->commit();
+ }
+ $id = $this->getId($key);
+
+ $f = $this->createCacheItem;
+ $isHit = false;
+ $value = null;
+
+ try {
+ foreach ($this->doFetch([$id]) as $value) {
+ $isHit = true;
+ }
+
+ return $f($key, $value, $isHit);
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
+ }
+
+ return $f($key, null, false);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ if ($this->deferred) {
+ $this->commit();
+ }
+ $ids = [];
+
+ foreach ($keys as $key) {
+ $ids[] = $this->getId($key);
+ }
+ try {
+ $items = $this->doFetch($ids);
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e]);
+ $items = [];
+ }
+ $ids = array_combine($ids, $keys);
+
+ return $this->generateItems($items, $ids);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ if (!$item instanceof CacheItem) {
+ return false;
+ }
+ $this->deferred[$item->getKey()] = $item;
+
+ return $this->commit();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ if (!$item instanceof CacheItem) {
+ return false;
+ }
+ $this->deferred[$item->getKey()] = $item;
+
+ return true;
+ }
+
+ public function __sleep()
+ {
+ throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+ }
+
+ public function __wakeup()
+ {
+ throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+ }
+
+ public function __destruct()
+ {
+ if ($this->deferred) {
+ $this->commit();
+ }
+ }
+
+ private function generateItems(iterable $items, array &$keys): iterable
+ {
+ $f = $this->createCacheItem;
+
+ try {
+ foreach ($items as $id => $value) {
+ if (!isset($keys[$id])) {
+ $id = key($keys);
+ }
+ $key = $keys[$id];
+ unset($keys[$id]);
+ yield $key => $f($key, $value, true);
+ }
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e]);
+ }
+
+ foreach ($keys as $key) {
+ yield $key => $f($key, null, false);
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/Traits/AbstractTrait.php b/console/skel/symfony/cache/Traits/AbstractTrait.php
new file mode 100644
index 0000000..7639424
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/AbstractTrait.php
@@ -0,0 +1,303 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+use Psr\Log\LoggerAwareTrait;
+use Symfony\Component\Cache\CacheItem;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait AbstractTrait
+{
+ use LoggerAwareTrait;
+
+ private $namespace;
+ private $namespaceVersion = '';
+ private $versioningIsEnabled = false;
+ private $deferred = [];
+ private $ids = [];
+
+ /**
+ * @var int|null The maximum length to enforce for identifiers or null when no limit applies
+ */
+ protected $maxIdLength;
+
+ /**
+ * Fetches several cache items.
+ *
+ * @param array $ids The cache identifiers to fetch
+ *
+ * @return array|\Traversable The corresponding values found in the cache
+ */
+ abstract protected function doFetch(array $ids);
+
+ /**
+ * Confirms if the cache contains specified cache item.
+ *
+ * @param string $id The identifier for which to check existence
+ *
+ * @return bool True if item exists in the cache, false otherwise
+ */
+ abstract protected function doHave($id);
+
+ /**
+ * Deletes all items in the pool.
+ *
+ * @param string $namespace The prefix used for all identifiers managed by this pool
+ *
+ * @return bool True if the pool was successfully cleared, false otherwise
+ */
+ abstract protected function doClear($namespace);
+
+ /**
+ * Removes multiple items from the pool.
+ *
+ * @param array $ids An array of identifiers that should be removed from the pool
+ *
+ * @return bool True if the items were successfully removed, false otherwise
+ */
+ abstract protected function doDelete(array $ids);
+
+ /**
+ * Persists several cache items immediately.
+ *
+ * @param array $values The values to cache, indexed by their cache identifier
+ * @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning
+ *
+ * @return array|bool The identifiers that failed to be cached or a boolean stating if caching succeeded or not
+ */
+ abstract protected function doSave(array $values, $lifetime);
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ $id = $this->getId($key);
+
+ if (isset($this->deferred[$key])) {
+ $this->commit();
+ }
+
+ try {
+ return $this->doHave($id);
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached: '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
+
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
+ */
+ public function clear(/*string $prefix = ''*/)
+ {
+ $this->deferred = [];
+ if ($cleared = $this->versioningIsEnabled) {
+ if ('' === $namespaceVersionToClear = $this->namespaceVersion) {
+ foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) {
+ $namespaceVersionToClear = $v;
+ }
+ }
+ $namespaceToClear = $this->namespace.$namespaceVersionToClear;
+ $namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), static::NS_SEPARATOR, 5);
+ try {
+ $cleared = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0);
+ } catch (\Exception $e) {
+ $cleared = false;
+ }
+ if ($cleared = true === $cleared || [] === $cleared) {
+ $this->namespaceVersion = $namespaceVersion;
+ $this->ids = [];
+ }
+ } else {
+ $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
+ $namespaceToClear = $this->namespace.$prefix;
+ }
+
+ try {
+ return $this->doClear($namespaceToClear) || $cleared;
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e]);
+
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItem($key)
+ {
+ return $this->deleteItems([$key]);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItems(array $keys)
+ {
+ $ids = [];
+
+ foreach ($keys as $key) {
+ $ids[$key] = $this->getId($key);
+ unset($this->deferred[$key]);
+ }
+
+ try {
+ if ($this->doDelete($ids)) {
+ return true;
+ }
+ } catch (\Exception $e) {
+ }
+
+ $ok = true;
+
+ // When bulk-delete failed, retry each item individually
+ foreach ($ids as $key => $id) {
+ try {
+ $e = null;
+ if ($this->doDelete([$id])) {
+ continue;
+ }
+ } catch (\Exception $e) {
+ }
+ $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]);
+ $ok = false;
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Enables/disables versioning of items.
+ *
+ * When versioning is enabled, clearing the cache is atomic and doesn't require listing existing keys to proceed,
+ * but old keys may need garbage collection and extra round-trips to the back-end are required.
+ *
+ * Calling this method also clears the memoized namespace version and thus forces a resynchonization of it.
+ *
+ * @param bool $enable
+ *
+ * @return bool the previous state of versioning
+ */
+ public function enableVersioning($enable = true)
+ {
+ $wasEnabled = $this->versioningIsEnabled;
+ $this->versioningIsEnabled = (bool) $enable;
+ $this->namespaceVersion = '';
+ $this->ids = [];
+
+ return $wasEnabled;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ if ($this->deferred) {
+ $this->commit();
+ }
+ $this->namespaceVersion = '';
+ $this->ids = [];
+ }
+
+ /**
+ * Like the native unserialize() function but throws an exception if anything goes wrong.
+ *
+ * @param string $value
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ *
+ * @deprecated since Symfony 4.2, use DefaultMarshaller instead.
+ */
+ protected static function unserialize($value)
+ {
+ @trigger_error(sprintf('The "%s::unserialize()" method is deprecated since Symfony 4.2, use DefaultMarshaller instead.', __CLASS__), E_USER_DEPRECATED);
+
+ if ('b:0;' === $value) {
+ return false;
+ }
+ $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
+ try {
+ if (false !== $value = unserialize($value)) {
+ return $value;
+ }
+ throw new \DomainException('Failed to unserialize cached value');
+ } catch (\Error $e) {
+ throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
+ } finally {
+ ini_set('unserialize_callback_func', $unserializeCallbackHandler);
+ }
+ }
+
+ private function getId($key): string
+ {
+ if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
+ $this->ids = [];
+ $this->namespaceVersion = '1'.static::NS_SEPARATOR;
+ try {
+ foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) {
+ $this->namespaceVersion = $v;
+ }
+ if ('1'.static::NS_SEPARATOR === $this->namespaceVersion) {
+ $this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), static::NS_SEPARATOR, 5);
+ $this->doSave([static::NS_SEPARATOR.$this->namespace => $this->namespaceVersion], 0);
+ }
+ } catch (\Exception $e) {
+ }
+ }
+
+ if (\is_string($key) && isset($this->ids[$key])) {
+ return $this->namespace.$this->namespaceVersion.$this->ids[$key];
+ }
+ CacheItem::validateKey($key);
+ $this->ids[$key] = $key;
+
+ if (null === $this->maxIdLength) {
+ return $this->namespace.$this->namespaceVersion.$key;
+ }
+ if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) {
+ // Use MD5 to favor speed over security, which is not an issue here
+ $this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2));
+ $id = $this->namespace.$this->namespaceVersion.$id;
+ }
+
+ return $id;
+ }
+
+ /**
+ * @internal
+ */
+ public static function handleUnserializeCallback($class)
+ {
+ throw new \DomainException('Class not found: '.$class);
+ }
+}
diff --git a/console/skel/symfony/cache/Traits/ApcuTrait.php b/console/skel/symfony/cache/Traits/ApcuTrait.php
new file mode 100644
index 0000000..c55def6
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/ApcuTrait.php
@@ -0,0 +1,121 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\CacheException;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait ApcuTrait
+{
+ public static function isSupported()
+ {
+ return \function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN);
+ }
+
+ private function init(string $namespace, int $defaultLifetime, ?string $version)
+ {
+ if (!static::isSupported()) {
+ throw new CacheException('APCu is not enabled');
+ }
+ if ('cli' === \PHP_SAPI) {
+ ini_set('apc.use_request_time', 0);
+ }
+ parent::__construct($namespace, $defaultLifetime);
+
+ if (null !== $version) {
+ CacheItem::validateKey($version);
+
+ if (!apcu_exists($version.'@'.$namespace)) {
+ $this->doClear($namespace);
+ apcu_add($version.'@'.$namespace, null);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
+ try {
+ $values = [];
+ foreach (apcu_fetch($ids, $ok) ?: [] as $k => $v) {
+ if (null !== $v || $ok) {
+ $values[$k] = $v;
+ }
+ }
+
+ return $values;
+ } catch (\Error $e) {
+ throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
+ } finally {
+ ini_set('unserialize_callback_func', $unserializeCallbackHandler);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave($id)
+ {
+ return apcu_exists($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear($namespace)
+ {
+ return isset($namespace[0]) && class_exists('APCuIterator', false) && ('cli' !== \PHP_SAPI || filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN))
+ ? apcu_delete(new \APCuIterator(sprintf('/^%s/', preg_quote($namespace, '/')), APC_ITER_KEY))
+ : apcu_clear_cache();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ foreach ($ids as $id) {
+ apcu_delete($id);
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, $lifetime)
+ {
+ try {
+ if (false === $failures = apcu_store($values, null, $lifetime)) {
+ $failures = $values;
+ }
+
+ return array_keys($failures);
+ } catch (\Throwable $e) {
+ if (1 === \count($values)) {
+ // Workaround https://github.com/krakjoe/apcu/issues/170
+ apcu_delete(key($values));
+ }
+
+ throw $e;
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/Traits/ArrayTrait.php b/console/skel/symfony/cache/Traits/ArrayTrait.php
new file mode 100644
index 0000000..21872c5
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/ArrayTrait.php
@@ -0,0 +1,183 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+use Psr\Log\LoggerAwareTrait;
+use Symfony\Component\Cache\CacheItem;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait ArrayTrait
+{
+ use LoggerAwareTrait;
+
+ private $storeSerialized;
+ private $values = [];
+ private $expiries = [];
+
+ /**
+ * Returns all cached values, with cache miss as null.
+ *
+ * @return array
+ */
+ public function getValues()
+ {
+ if (!$this->storeSerialized) {
+ return $this->values;
+ }
+
+ $values = $this->values;
+ foreach ($values as $k => $v) {
+ if (null === $v || 'N;' === $v) {
+ continue;
+ }
+ if (!\is_string($v) || !isset($v[2]) || ':' !== $v[1]) {
+ $values[$k] = serialize($v);
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > microtime(true)) {
+ return true;
+ }
+ CacheItem::validateKey($key);
+
+ return isset($this->expiries[$key]) && !$this->deleteItem($key);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
+ */
+ public function clear(/*string $prefix = ''*/)
+ {
+ $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
+
+ if ('' !== $prefix) {
+ foreach ($this->values as $key => $value) {
+ if (0 === strpos($key, $prefix)) {
+ unset($this->values[$key], $this->expiries[$key]);
+ }
+ }
+ } else {
+ $this->values = $this->expiries = [];
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItem($key)
+ {
+ if (!\is_string($key) || !isset($this->expiries[$key])) {
+ CacheItem::validateKey($key);
+ }
+ unset($this->values[$key], $this->expiries[$key]);
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ $this->clear();
+ }
+
+ private function generateItems(array $keys, float $now, callable $f): iterable
+ {
+ foreach ($keys as $i => $key) {
+ if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) {
+ $this->values[$key] = $value = null;
+ } else {
+ $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
+ }
+ unset($keys[$i]);
+
+ yield $key => $f($key, $value, $isHit);
+ }
+
+ foreach ($keys as $key) {
+ yield $key => $f($key, null, false);
+ }
+ }
+
+ private function freeze($value, $key)
+ {
+ if (null === $value) {
+ return 'N;';
+ }
+ if (\is_string($value)) {
+ // Serialize strings if they could be confused with serialized objects or arrays
+ if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
+ return serialize($value);
+ }
+ } elseif (!is_scalar($value)) {
+ try {
+ $serialized = serialize($value);
+ } catch (\Exception $e) {
+ $type = \is_object($value) ? \get_class($value) : \gettype($value);
+ $message = sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage());
+ CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]);
+
+ return null;
+ }
+ // Keep value serialized if it contains any objects or any internal references
+ if ('C' === $serialized[0] || 'O' === $serialized[0] || preg_match('/;[OCRr]:[1-9]/', $serialized)) {
+ return $serialized;
+ }
+ }
+
+ return $value;
+ }
+
+ private function unfreeze(string $key, bool &$isHit)
+ {
+ if ('N;' === $value = $this->values[$key]) {
+ return null;
+ }
+ if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
+ try {
+ $value = unserialize($value);
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
+ $value = false;
+ }
+ if (false === $value) {
+ $this->values[$key] = $value = null;
+ $isHit = false;
+ }
+ }
+
+ return $value;
+ }
+}
diff --git a/console/skel/symfony/cache/Traits/ContractsTrait.php b/console/skel/symfony/cache/Traits/ContractsTrait.php
new file mode 100644
index 0000000..06070c9
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/ContractsTrait.php
@@ -0,0 +1,97 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Cache\Adapter\AdapterInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\LockRegistry;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Cache\CacheTrait;
+use Symfony\Contracts\Cache\ItemInterface;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait ContractsTrait
+{
+ use CacheTrait {
+ doGet as private contractsGet;
+ }
+
+ private $callbackWrapper = [LockRegistry::class, 'compute'];
+ private $computing = [];
+
+ /**
+ * Wraps the callback passed to ->get() in a callable.
+ *
+ * @return callable the previous callback wrapper
+ */
+ public function setCallbackWrapper(?callable $callbackWrapper): callable
+ {
+ $previousWrapper = $this->callbackWrapper;
+ $this->callbackWrapper = $callbackWrapper ?? function (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) {
+ return $callback($item, $save);
+ };
+
+ return $previousWrapper;
+ }
+
+ private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null)
+ {
+ if (0 > $beta = $beta ?? 1.0) {
+ throw new InvalidArgumentException(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta));
+ }
+
+ static $setMetadata;
+
+ $setMetadata = $setMetadata ?? \Closure::bind(
+ static function (CacheItem $item, float $startTime, ?array &$metadata) {
+ if ($item->expiry > $endTime = microtime(true)) {
+ $item->newMetadata[CacheItem::METADATA_EXPIRY] = $metadata[CacheItem::METADATA_EXPIRY] = $item->expiry;
+ $item->newMetadata[CacheItem::METADATA_CTIME] = $metadata[CacheItem::METADATA_CTIME] = (int) ceil(1000 * ($endTime - $startTime));
+ } else {
+ unset($metadata[CacheItem::METADATA_EXPIRY], $metadata[CacheItem::METADATA_CTIME]);
+ }
+ },
+ null,
+ CacheItem::class
+ );
+
+ return $this->contractsGet($pool, $key, function (CacheItem $item, bool &$save) use ($pool, $callback, $setMetadata, &$metadata, $key) {
+ // don't wrap nor save recursive calls
+ if (isset($this->computing[$key])) {
+ $value = $callback($item, $save);
+ $save = false;
+
+ return $value;
+ }
+
+ $this->computing[$key] = $key;
+ $startTime = microtime(true);
+
+ try {
+ $value = ($this->callbackWrapper)($callback, $item, $save, $pool, function (CacheItem $item) use ($setMetadata, $startTime, &$metadata) {
+ $setMetadata($item, $startTime, $metadata);
+ }, $this->logger ?? null);
+ $setMetadata($item, $startTime, $metadata);
+
+ return $value;
+ } finally {
+ unset($this->computing[$key]);
+ }
+ }, $beta, $metadata, $this->logger ?? null);
+ }
+}
diff --git a/console/skel/symfony/cache/Traits/DoctrineTrait.php b/console/skel/symfony/cache/Traits/DoctrineTrait.php
new file mode 100644
index 0000000..c87ecab
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/DoctrineTrait.php
@@ -0,0 +1,98 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait DoctrineTrait
+{
+ private $provider;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ parent::reset();
+ $this->provider->setNamespace($this->provider->getNamespace());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ $unserializeCallbackHandler = ini_set('unserialize_callback_func', parent::class.'::handleUnserializeCallback');
+ try {
+ return $this->provider->fetchMultiple($ids);
+ } catch (\Error $e) {
+ $trace = $e->getTrace();
+
+ if (isset($trace[0]['function']) && !isset($trace[0]['class'])) {
+ switch ($trace[0]['function']) {
+ case 'unserialize':
+ case 'apcu_fetch':
+ case 'apc_fetch':
+ throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
+ }
+ }
+
+ throw $e;
+ } finally {
+ ini_set('unserialize_callback_func', $unserializeCallbackHandler);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave($id)
+ {
+ return $this->provider->contains($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear($namespace)
+ {
+ $namespace = $this->provider->getNamespace();
+
+ return isset($namespace[0])
+ ? $this->provider->deleteAll()
+ : $this->provider->flushAll();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ $ok = true;
+ foreach ($ids as $id) {
+ $ok = $this->provider->delete($id) && $ok;
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, $lifetime)
+ {
+ return $this->provider->saveMultiple($values, $lifetime);
+ }
+}
diff --git a/console/skel/symfony/cache/Traits/FilesystemCommonTrait.php b/console/skel/symfony/cache/Traits/FilesystemCommonTrait.php
new file mode 100644
index 0000000..15cc950
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/FilesystemCommonTrait.php
@@ -0,0 +1,185 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait FilesystemCommonTrait
+{
+ private $directory;
+ private $tmp;
+
+ private function init(string $namespace, ?string $directory)
+ {
+ if (!isset($directory[0])) {
+ $directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache';
+ } else {
+ $directory = realpath($directory) ?: $directory;
+ }
+ if (isset($namespace[0])) {
+ if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) {
+ throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
+ }
+ $directory .= \DIRECTORY_SEPARATOR.$namespace;
+ } else {
+ $directory .= \DIRECTORY_SEPARATOR.'@';
+ }
+ if (!file_exists($directory)) {
+ @mkdir($directory, 0777, true);
+ }
+ $directory .= \DIRECTORY_SEPARATOR;
+ // On Windows the whole path is limited to 258 chars
+ if ('\\' === \DIRECTORY_SEPARATOR && \strlen($directory) > 234) {
+ throw new InvalidArgumentException(sprintf('Cache directory too long (%s)', $directory));
+ }
+
+ $this->directory = $directory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear($namespace)
+ {
+ $ok = true;
+
+ foreach ($this->scanHashDir($this->directory) as $file) {
+ if ('' !== $namespace && 0 !== strpos($this->getFileKey($file), $namespace)) {
+ continue;
+ }
+
+ $ok = ($this->doUnlink($file) || !file_exists($file)) && $ok;
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ $ok = true;
+
+ foreach ($ids as $id) {
+ $file = $this->getFile($id);
+ $ok = (!file_exists($file) || $this->doUnlink($file) || !file_exists($file)) && $ok;
+ }
+
+ return $ok;
+ }
+
+ protected function doUnlink($file)
+ {
+ return @unlink($file);
+ }
+
+ private function write(string $file, string $data, int $expiresAt = null)
+ {
+ set_error_handler(__CLASS__.'::throwError');
+ try {
+ if (null === $this->tmp) {
+ $this->tmp = $this->directory.uniqid('', true);
+ }
+ file_put_contents($this->tmp, $data);
+
+ if (null !== $expiresAt) {
+ touch($this->tmp, $expiresAt);
+ }
+
+ return rename($this->tmp, $file);
+ } finally {
+ restore_error_handler();
+ }
+ }
+
+ private function getFile(string $id, bool $mkdir = false, string $directory = null)
+ {
+ // Use MD5 to favor speed over security, which is not an issue here
+ $hash = str_replace('/', '-', base64_encode(hash('md5', static::class.$id, true)));
+ $dir = ($directory ?? $this->directory).strtoupper($hash[0].\DIRECTORY_SEPARATOR.$hash[1].\DIRECTORY_SEPARATOR);
+
+ if ($mkdir && !file_exists($dir)) {
+ @mkdir($dir, 0777, true);
+ }
+
+ return $dir.substr($hash, 2, 20);
+ }
+
+ private function getFileKey(string $file): string
+ {
+ return '';
+ }
+
+ private function scanHashDir(string $directory): \Generator
+ {
+ if (!file_exists($directory)) {
+ return;
+ }
+
+ $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+ for ($i = 0; $i < 38; ++$i) {
+ if (!file_exists($directory.$chars[$i])) {
+ continue;
+ }
+
+ for ($j = 0; $j < 38; ++$j) {
+ if (!file_exists($dir = $directory.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) {
+ continue;
+ }
+
+ foreach (@scandir($dir, SCANDIR_SORT_NONE) ?: [] as $file) {
+ if ('.' !== $file && '..' !== $file) {
+ yield $dir.\DIRECTORY_SEPARATOR.$file;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @internal
+ */
+ public static function throwError($type, $message, $file, $line)
+ {
+ throw new \ErrorException($message, 0, $type, $file, $line);
+ }
+
+ /**
+ * @return array
+ */
+ public function __sleep()
+ {
+ throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+ }
+
+ public function __wakeup()
+ {
+ throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+ }
+
+ public function __destruct()
+ {
+ if (method_exists(parent::class, '__destruct')) {
+ parent::__destruct();
+ }
+ if (null !== $this->tmp && file_exists($this->tmp)) {
+ unlink($this->tmp);
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/Traits/FilesystemTrait.php b/console/skel/symfony/cache/Traits/FilesystemTrait.php
new file mode 100644
index 0000000..185eb00
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/FilesystemTrait.php
@@ -0,0 +1,124 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+use Symfony\Component\Cache\Exception\CacheException;
+
+/**
+ * @author Nicolas Grekas
+ * @author Rob Frawley 2nd
+ *
+ * @internal
+ */
+trait FilesystemTrait
+{
+ use FilesystemCommonTrait;
+
+ private $marshaller;
+
+ /**
+ * @return bool
+ */
+ public function prune()
+ {
+ $time = time();
+ $pruned = true;
+
+ foreach ($this->scanHashDir($this->directory) as $file) {
+ if (!$h = @fopen($file, 'rb')) {
+ continue;
+ }
+
+ if (($expiresAt = (int) fgets($h)) && $time >= $expiresAt) {
+ fclose($h);
+ $pruned = @unlink($file) && !file_exists($file) && $pruned;
+ } else {
+ fclose($h);
+ }
+ }
+
+ return $pruned;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ $values = [];
+ $now = time();
+
+ foreach ($ids as $id) {
+ $file = $this->getFile($id);
+ if (!file_exists($file) || !$h = @fopen($file, 'rb')) {
+ continue;
+ }
+ if (($expiresAt = (int) fgets($h)) && $now >= $expiresAt) {
+ fclose($h);
+ @unlink($file);
+ } else {
+ $i = rawurldecode(rtrim(fgets($h)));
+ $value = stream_get_contents($h);
+ fclose($h);
+ if ($i === $id) {
+ $values[$id] = $this->marshaller->unmarshall($value);
+ }
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave($id)
+ {
+ $file = $this->getFile($id);
+
+ return file_exists($file) && (@filemtime($file) > time() || $this->doFetch([$id]));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, $lifetime)
+ {
+ $expiresAt = $lifetime ? (time() + $lifetime) : 0;
+ $values = $this->marshaller->marshall($values, $failed);
+
+ foreach ($values as $id => $value) {
+ if (!$this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".$value, $expiresAt)) {
+ $failed[] = $id;
+ }
+ }
+
+ if ($failed && !is_writable($this->directory)) {
+ throw new CacheException(sprintf('Cache directory is not writable (%s)', $this->directory));
+ }
+
+ return $failed;
+ }
+
+ private function getFileKey(string $file): string
+ {
+ if (!$h = @fopen($file, 'rb')) {
+ return '';
+ }
+
+ fgets($h); // expiry
+ $encodedKey = fgets($h);
+ fclose($h);
+
+ return rawurldecode(rtrim($encodedKey));
+ }
+}
diff --git a/console/skel/symfony/cache/Traits/MemcachedTrait.php b/console/skel/symfony/cache/Traits/MemcachedTrait.php
new file mode 100644
index 0000000..070eb0e
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/MemcachedTrait.php
@@ -0,0 +1,325 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+use Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+
+/**
+ * @author Rob Frawley 2nd
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait MemcachedTrait
+{
+ private static $defaultClientOptions = [
+ 'persistent_id' => null,
+ 'username' => null,
+ 'password' => null,
+ \Memcached::OPT_SERIALIZER => \Memcached::SERIALIZER_PHP,
+ ];
+
+ private $marshaller;
+ private $client;
+ private $lazyClient;
+
+ public static function isSupported()
+ {
+ return \extension_loaded('memcached') && version_compare(phpversion('memcached'), '2.2.0', '>=');
+ }
+
+ private function init(\Memcached $client, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller)
+ {
+ if (!static::isSupported()) {
+ throw new CacheException('Memcached >= 2.2.0 is required');
+ }
+ if ('Memcached' === \get_class($client)) {
+ $opt = $client->getOption(\Memcached::OPT_SERIALIZER);
+ if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) {
+ throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
+ }
+ $this->maxIdLength -= \strlen($client->getOption(\Memcached::OPT_PREFIX_KEY));
+ $this->client = $client;
+ } else {
+ $this->lazyClient = $client;
+ }
+
+ parent::__construct($namespace, $defaultLifetime);
+ $this->enableVersioning();
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+ }
+
+ /**
+ * Creates a Memcached instance.
+ *
+ * By default, the binary protocol, no block, and libketama compatible options are enabled.
+ *
+ * Examples for servers:
+ * - 'memcached://user:pass@localhost?weight=33'
+ * - [['localhost', 11211, 33]]
+ *
+ * @param array[]|string|string[] $servers An array of servers, a DSN, or an array of DSNs
+ *
+ * @return \Memcached
+ *
+ * @throws \ErrorException When invalid options or servers are provided
+ */
+ public static function createConnection($servers, array $options = [])
+ {
+ if (\is_string($servers)) {
+ $servers = [$servers];
+ } elseif (!\is_array($servers)) {
+ throw new InvalidArgumentException(sprintf('MemcachedAdapter::createClient() expects array or string as first argument, %s given.', \gettype($servers)));
+ }
+ if (!static::isSupported()) {
+ throw new CacheException('Memcached >= 2.2.0 is required');
+ }
+ set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); });
+ try {
+ $options += static::$defaultClientOptions;
+ $client = new \Memcached($options['persistent_id']);
+ $username = $options['username'];
+ $password = $options['password'];
+
+ // parse any DSN in $servers
+ foreach ($servers as $i => $dsn) {
+ if (\is_array($dsn)) {
+ continue;
+ }
+ if (0 !== strpos($dsn, 'memcached:')) {
+ throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s does not start with "memcached:"', $dsn));
+ }
+ $params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) {
+ if (!empty($m[2])) {
+ list($username, $password) = explode(':', $m[2], 2) + [1 => null];
+ }
+
+ return 'file:'.($m[1] ?? '');
+ }, $dsn);
+ if (false === $params = parse_url($params)) {
+ throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
+ }
+ $query = $hosts = [];
+ if (isset($params['query'])) {
+ parse_str($params['query'], $query);
+
+ if (isset($query['host'])) {
+ if (!\is_array($hosts = $query['host'])) {
+ throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
+ }
+ foreach ($hosts as $host => $weight) {
+ if (false === $port = strrpos($host, ':')) {
+ $hosts[$host] = [$host, 11211, (int) $weight];
+ } else {
+ $hosts[$host] = [substr($host, 0, $port), (int) substr($host, 1 + $port), (int) $weight];
+ }
+ }
+ $hosts = array_values($hosts);
+ unset($query['host']);
+ }
+ if ($hosts && !isset($params['host']) && !isset($params['path'])) {
+ unset($servers[$i]);
+ $servers = array_merge($servers, $hosts);
+ continue;
+ }
+ }
+ if (!isset($params['host']) && !isset($params['path'])) {
+ throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
+ }
+ if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
+ $params['weight'] = $m[1];
+ $params['path'] = substr($params['path'], 0, -\strlen($m[0]));
+ }
+ $params += [
+ 'host' => isset($params['host']) ? $params['host'] : $params['path'],
+ 'port' => isset($params['host']) ? 11211 : null,
+ 'weight' => 0,
+ ];
+ if ($query) {
+ $params += $query;
+ $options = $query + $options;
+ }
+
+ $servers[$i] = [$params['host'], $params['port'], $params['weight']];
+
+ if ($hosts) {
+ $servers = array_merge($servers, $hosts);
+ }
+ }
+
+ // set client's options
+ unset($options['persistent_id'], $options['username'], $options['password'], $options['weight'], $options['lazy']);
+ $options = array_change_key_case($options, CASE_UPPER);
+ $client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
+ $client->setOption(\Memcached::OPT_NO_BLOCK, true);
+ $client->setOption(\Memcached::OPT_TCP_NODELAY, true);
+ if (!\array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !\array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) {
+ $client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
+ }
+ foreach ($options as $name => $value) {
+ if (\is_int($name)) {
+ continue;
+ }
+ if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) {
+ $value = \constant('Memcached::'.$name.'_'.strtoupper($value));
+ }
+ $opt = \constant('Memcached::OPT_'.$name);
+
+ unset($options[$name]);
+ $options[$opt] = $value;
+ }
+ $client->setOptions($options);
+
+ // set client's servers, taking care of persistent connections
+ if (!$client->isPristine()) {
+ $oldServers = [];
+ foreach ($client->getServerList() as $server) {
+ $oldServers[] = [$server['host'], $server['port']];
+ }
+
+ $newServers = [];
+ foreach ($servers as $server) {
+ if (1 < \count($server)) {
+ $server = array_values($server);
+ unset($server[2]);
+ $server[1] = (int) $server[1];
+ }
+ $newServers[] = $server;
+ }
+
+ if ($oldServers !== $newServers) {
+ $client->resetServerList();
+ $client->addServers($servers);
+ }
+ } else {
+ $client->addServers($servers);
+ }
+
+ if (null !== $username || null !== $password) {
+ if (!method_exists($client, 'setSaslAuthData')) {
+ trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.');
+ }
+ $client->setSaslAuthData($username, $password);
+ }
+
+ return $client;
+ } finally {
+ restore_error_handler();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, $lifetime)
+ {
+ if (!$values = $this->marshaller->marshall($values, $failed)) {
+ return $failed;
+ }
+
+ if ($lifetime && $lifetime > 30 * 86400) {
+ $lifetime += time();
+ }
+
+ $encodedValues = [];
+ foreach ($values as $key => $value) {
+ $encodedValues[rawurlencode($key)] = $value;
+ }
+
+ return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)) ? $failed : false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ try {
+ $encodedIds = array_map('rawurlencode', $ids);
+
+ $encodedResult = $this->checkResultCode($this->getClient()->getMulti($encodedIds));
+
+ $result = [];
+ foreach ($encodedResult as $key => $value) {
+ $result[rawurldecode($key)] = $this->marshaller->unmarshall($value);
+ }
+
+ return $result;
+ } catch (\Error $e) {
+ throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave($id)
+ {
+ return false !== $this->getClient()->get(rawurlencode($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ $ok = true;
+ $encodedIds = array_map('rawurlencode', $ids);
+ foreach ($this->checkResultCode($this->getClient()->deleteMulti($encodedIds)) as $result) {
+ if (\Memcached::RES_SUCCESS !== $result && \Memcached::RES_NOTFOUND !== $result) {
+ $ok = false;
+ break;
+ }
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear($namespace)
+ {
+ return '' === $namespace && $this->getClient()->flush();
+ }
+
+ private function checkResultCode($result)
+ {
+ $code = $this->client->getResultCode();
+
+ if (\Memcached::RES_SUCCESS === $code || \Memcached::RES_NOTFOUND === $code) {
+ return $result;
+ }
+
+ throw new CacheException(sprintf('MemcachedAdapter client error: %s.', strtolower($this->client->getResultMessage())));
+ }
+
+ private function getClient(): \Memcached
+ {
+ if ($this->client) {
+ return $this->client;
+ }
+
+ $opt = $this->lazyClient->getOption(\Memcached::OPT_SERIALIZER);
+ if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) {
+ throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
+ }
+ if ('' !== $prefix = (string) $this->lazyClient->getOption(\Memcached::OPT_PREFIX_KEY)) {
+ throw new CacheException(sprintf('MemcachedAdapter: "prefix_key" option must be empty when using proxified connections, "%s" given.', $prefix));
+ }
+
+ return $this->client = $this->lazyClient;
+ }
+}
diff --git a/console/skel/symfony/cache/Traits/PdoTrait.php b/console/skel/symfony/cache/Traits/PdoTrait.php
new file mode 100644
index 0000000..f927b4e
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/PdoTrait.php
@@ -0,0 +1,446 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+use Doctrine\DBAL\Connection;
+use Doctrine\DBAL\DBALException;
+use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
+use Doctrine\DBAL\DriverManager;
+use Doctrine\DBAL\Exception\TableNotFoundException;
+use Doctrine\DBAL\Schema\Schema;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+
+/**
+ * @internal
+ */
+trait PdoTrait
+{
+ private $marshaller;
+ private $conn;
+ private $dsn;
+ private $driver;
+ private $serverVersion;
+ private $table = 'cache_items';
+ private $idCol = 'item_id';
+ private $dataCol = 'item_data';
+ private $lifetimeCol = 'item_lifetime';
+ private $timeCol = 'item_time';
+ private $username = '';
+ private $password = '';
+ private $connectionOptions = [];
+ private $namespace;
+
+ private function init($connOrDsn, string $namespace, int $defaultLifetime, array $options, ?MarshallerInterface $marshaller)
+ {
+ if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) {
+ throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0]));
+ }
+
+ if ($connOrDsn instanceof \PDO) {
+ if (\PDO::ERRMODE_EXCEPTION !== $connOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) {
+ throw new InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__));
+ }
+
+ $this->conn = $connOrDsn;
+ } elseif ($connOrDsn instanceof Connection) {
+ $this->conn = $connOrDsn;
+ } elseif (\is_string($connOrDsn)) {
+ $this->dsn = $connOrDsn;
+ } else {
+ throw new InvalidArgumentException(sprintf('"%s" requires PDO or Doctrine\DBAL\Connection instance or DSN string as first argument, "%s" given.', __CLASS__, \is_object($connOrDsn) ? \get_class($connOrDsn) : \gettype($connOrDsn)));
+ }
+
+ $this->table = isset($options['db_table']) ? $options['db_table'] : $this->table;
+ $this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol;
+ $this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol;
+ $this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol;
+ $this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol;
+ $this->username = isset($options['db_username']) ? $options['db_username'] : $this->username;
+ $this->password = isset($options['db_password']) ? $options['db_password'] : $this->password;
+ $this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions;
+ $this->namespace = $namespace;
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+
+ parent::__construct($namespace, $defaultLifetime);
+ }
+
+ /**
+ * Creates the table to store cache items which can be called once for setup.
+ *
+ * Cache ID are saved in a column of maximum length 255. Cache data is
+ * saved in a BLOB.
+ *
+ * @throws \PDOException When the table already exists
+ * @throws DBALException When the table already exists
+ * @throws \DomainException When an unsupported PDO driver is used
+ */
+ public function createTable()
+ {
+ // connect if we are not yet
+ $conn = $this->getConnection();
+
+ if ($conn instanceof Connection) {
+ $types = [
+ 'mysql' => 'binary',
+ 'sqlite' => 'text',
+ 'pgsql' => 'string',
+ 'oci' => 'string',
+ 'sqlsrv' => 'string',
+ ];
+ if (!isset($types[$this->driver])) {
+ throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
+ }
+
+ $schema = new Schema();
+ $table = $schema->createTable($this->table);
+ $table->addColumn($this->idCol, $types[$this->driver], ['length' => 255]);
+ $table->addColumn($this->dataCol, 'blob', ['length' => 16777215]);
+ $table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]);
+ $table->addColumn($this->timeCol, 'integer', ['unsigned' => true]);
+ $table->setPrimaryKey([$this->idCol]);
+
+ foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
+ $conn->exec($sql);
+ }
+
+ return;
+ }
+
+ switch ($this->driver) {
+ case 'mysql':
+ // We use varbinary for the ID column because it prevents unwanted conversions:
+ // - character set conversions between server and client
+ // - trailing space removal
+ // - case-insensitivity
+ // - language processing like é == e
+ $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(255) NOT NULL PRIMARY KEY, $this->dataCol MEDIUMBLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB";
+ break;
+ case 'sqlite':
+ $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
+ break;
+ case 'pgsql':
+ $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
+ break;
+ case 'oci':
+ $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(255) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
+ break;
+ case 'sqlsrv':
+ $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
+ break;
+ default:
+ throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
+ }
+
+ $conn->exec($sql);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prune()
+ {
+ $deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= :time";
+
+ if ('' !== $this->namespace) {
+ $deleteSql .= " AND $this->idCol LIKE :namespace";
+ }
+
+ try {
+ $delete = $this->getConnection()->prepare($deleteSql);
+ } catch (TableNotFoundException $e) {
+ return true;
+ } catch (\PDOException $e) {
+ return true;
+ }
+ $delete->bindValue(':time', time(), \PDO::PARAM_INT);
+
+ if ('' !== $this->namespace) {
+ $delete->bindValue(':namespace', sprintf('%s%%', $this->namespace), \PDO::PARAM_STR);
+ }
+ try {
+ return $delete->execute();
+ } catch (TableNotFoundException $e) {
+ return true;
+ } catch (\PDOException $e) {
+ return true;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ $now = time();
+ $expired = [];
+
+ $sql = str_pad('', (\count($ids) << 1) - 1, '?,');
+ $sql = "SELECT $this->idCol, CASE WHEN $this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ? THEN $this->dataCol ELSE NULL END FROM $this->table WHERE $this->idCol IN ($sql)";
+ $stmt = $this->getConnection()->prepare($sql);
+ $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT);
+ foreach ($ids as $id) {
+ $stmt->bindValue(++$i, $id);
+ }
+ $stmt->execute();
+
+ while ($row = $stmt->fetch(\PDO::FETCH_NUM)) {
+ if (null === $row[1]) {
+ $expired[] = $row[0];
+ } else {
+ yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]);
+ }
+ }
+
+ if ($expired) {
+ $sql = str_pad('', (\count($expired) << 1) - 1, '?,');
+ $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ? AND $this->idCol IN ($sql)";
+ $stmt = $this->getConnection()->prepare($sql);
+ $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT);
+ foreach ($expired as $id) {
+ $stmt->bindValue(++$i, $id);
+ }
+ $stmt->execute();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave($id)
+ {
+ $sql = "SELECT 1 FROM $this->table WHERE $this->idCol = :id AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > :time)";
+ $stmt = $this->getConnection()->prepare($sql);
+
+ $stmt->bindValue(':id', $id);
+ $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
+ $stmt->execute();
+
+ return (bool) $stmt->fetchColumn();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear($namespace)
+ {
+ $conn = $this->getConnection();
+
+ if ('' === $namespace) {
+ if ('sqlite' === $this->driver) {
+ $sql = "DELETE FROM $this->table";
+ } else {
+ $sql = "TRUNCATE TABLE $this->table";
+ }
+ } else {
+ $sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'";
+ }
+
+ try {
+ $conn->exec($sql);
+ } catch (TableNotFoundException $e) {
+ } catch (\PDOException $e) {
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ $sql = str_pad('', (\count($ids) << 1) - 1, '?,');
+ $sql = "DELETE FROM $this->table WHERE $this->idCol IN ($sql)";
+ try {
+ $stmt = $this->getConnection()->prepare($sql);
+ $stmt->execute(array_values($ids));
+ } catch (TableNotFoundException $e) {
+ } catch (\PDOException $e) {
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, $lifetime)
+ {
+ if (!$values = $this->marshaller->marshall($values, $failed)) {
+ return $failed;
+ }
+
+ $conn = $this->getConnection();
+ $driver = $this->driver;
+ $insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
+
+ switch (true) {
+ case 'mysql' === $driver:
+ $sql = $insertSql." ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)";
+ break;
+ case 'oci' === $driver:
+ // DUAL is Oracle specific dummy table
+ $sql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ".
+ "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
+ "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?";
+ break;
+ case 'sqlsrv' === $driver && version_compare($this->getServerVersion(), '10', '>='):
+ // MERGE is only available since SQL Server 2008 and must be terminated by semicolon
+ // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
+ $sql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ".
+ "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
+ "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;";
+ break;
+ case 'sqlite' === $driver:
+ $sql = 'INSERT OR REPLACE'.substr($insertSql, 6);
+ break;
+ case 'pgsql' === $driver && version_compare($this->getServerVersion(), '9.5', '>='):
+ $sql = $insertSql." ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)";
+ break;
+ default:
+ $driver = null;
+ $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id";
+ break;
+ }
+
+ $now = time();
+ $lifetime = $lifetime ?: null;
+ try {
+ $stmt = $conn->prepare($sql);
+ } catch (TableNotFoundException $e) {
+ if (!$conn->isTransactionActive() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
+ $this->createTable();
+ }
+ $stmt = $conn->prepare($sql);
+ } catch (\PDOException $e) {
+ if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
+ $this->createTable();
+ }
+ $stmt = $conn->prepare($sql);
+ }
+
+ if ('sqlsrv' === $driver || 'oci' === $driver) {
+ $stmt->bindParam(1, $id);
+ $stmt->bindParam(2, $id);
+ $stmt->bindParam(3, $data, \PDO::PARAM_LOB);
+ $stmt->bindValue(4, $lifetime, \PDO::PARAM_INT);
+ $stmt->bindValue(5, $now, \PDO::PARAM_INT);
+ $stmt->bindParam(6, $data, \PDO::PARAM_LOB);
+ $stmt->bindValue(7, $lifetime, \PDO::PARAM_INT);
+ $stmt->bindValue(8, $now, \PDO::PARAM_INT);
+ } else {
+ $stmt->bindParam(':id', $id);
+ $stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
+ $stmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT);
+ $stmt->bindValue(':time', $now, \PDO::PARAM_INT);
+ }
+ if (null === $driver) {
+ $insertStmt = $conn->prepare($insertSql);
+
+ $insertStmt->bindParam(':id', $id);
+ $insertStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
+ $insertStmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT);
+ $insertStmt->bindValue(':time', $now, \PDO::PARAM_INT);
+ }
+
+ foreach ($values as $id => $data) {
+ try {
+ $stmt->execute();
+ } catch (TableNotFoundException $e) {
+ if (!$conn->isTransactionActive() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
+ $this->createTable();
+ }
+ $stmt->execute();
+ } catch (\PDOException $e) {
+ if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
+ $this->createTable();
+ }
+ $stmt->execute();
+ }
+ if (null === $driver && !$stmt->rowCount()) {
+ try {
+ $insertStmt->execute();
+ } catch (DBALException $e) {
+ } catch (\PDOException $e) {
+ // A concurrent write won, let it be
+ }
+ }
+ }
+
+ return $failed;
+ }
+
+ /**
+ * @return \PDO|Connection
+ */
+ private function getConnection()
+ {
+ if (null === $this->conn) {
+ if (strpos($this->dsn, '://')) {
+ if (!class_exists(DriverManager::class)) {
+ throw new InvalidArgumentException(sprintf('Failed to parse the DSN "%s". Try running "composer require doctrine/dbal".', $this->dsn));
+ }
+ $this->conn = DriverManager::getConnection(['url' => $this->dsn]);
+ } else {
+ $this->conn = new \PDO($this->dsn, $this->username, $this->password, $this->connectionOptions);
+ $this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+ }
+ }
+ if (null === $this->driver) {
+ if ($this->conn instanceof \PDO) {
+ $this->driver = $this->conn->getAttribute(\PDO::ATTR_DRIVER_NAME);
+ } else {
+ switch ($this->driver = $this->conn->getDriver()->getName()) {
+ case 'mysqli':
+ throw new \LogicException(sprintf('The adapter "%s" does not support the mysqli driver, use pdo_mysql instead.', static::class));
+ case 'pdo_mysql':
+ case 'drizzle_pdo_mysql':
+ $this->driver = 'mysql';
+ break;
+ case 'pdo_sqlite':
+ $this->driver = 'sqlite';
+ break;
+ case 'pdo_pgsql':
+ $this->driver = 'pgsql';
+ break;
+ case 'oci8':
+ case 'pdo_oracle':
+ $this->driver = 'oci';
+ break;
+ case 'pdo_sqlsrv':
+ $this->driver = 'sqlsrv';
+ break;
+ }
+ }
+ }
+
+ return $this->conn;
+ }
+
+ private function getServerVersion(): string
+ {
+ if (null === $this->serverVersion) {
+ $conn = $this->conn instanceof \PDO ? $this->conn : $this->conn->getWrappedConnection();
+ if ($conn instanceof \PDO) {
+ $this->serverVersion = $conn->getAttribute(\PDO::ATTR_SERVER_VERSION);
+ } elseif ($conn instanceof ServerInfoAwareConnection) {
+ $this->serverVersion = $conn->getServerVersion();
+ } else {
+ $this->serverVersion = '0';
+ }
+ }
+
+ return $this->serverVersion;
+ }
+}
diff --git a/console/skel/symfony/cache/Traits/PhpArrayTrait.php b/console/skel/symfony/cache/Traits/PhpArrayTrait.php
new file mode 100644
index 0000000..6e7c72c
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/PhpArrayTrait.php
@@ -0,0 +1,169 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+use Symfony\Component\Cache\Adapter\AdapterInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\VarExporter\VarExporter;
+
+/**
+ * @author Titouan Galopin
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait PhpArrayTrait
+{
+ use ProxyTrait;
+
+ private $file;
+ private $keys;
+ private $values;
+
+ private static $valuesCache = [];
+
+ /**
+ * Store an array of cached values.
+ *
+ * @param array $values The cached values
+ */
+ public function warmUp(array $values)
+ {
+ if (file_exists($this->file)) {
+ if (!is_file($this->file)) {
+ throw new InvalidArgumentException(sprintf('Cache path exists and is not a file: %s.', $this->file));
+ }
+
+ if (!is_writable($this->file)) {
+ throw new InvalidArgumentException(sprintf('Cache file is not writable: %s.', $this->file));
+ }
+ } else {
+ $directory = \dirname($this->file);
+
+ if (!is_dir($directory) && !@mkdir($directory, 0777, true)) {
+ throw new InvalidArgumentException(sprintf('Cache directory does not exist and cannot be created: %s.', $directory));
+ }
+
+ if (!is_writable($directory)) {
+ throw new InvalidArgumentException(sprintf('Cache directory is not writable: %s.', $directory));
+ }
+ }
+
+ $dumpedValues = '';
+ $dumpedMap = [];
+ $dump = <<<'EOF'
+ $value) {
+ CacheItem::validateKey(\is_int($key) ? (string) $key : $key);
+ $isStaticValue = true;
+
+ if (null === $value) {
+ $value = "'N;'";
+ } elseif (\is_object($value) || \is_array($value)) {
+ try {
+ $value = VarExporter::export($value, $isStaticValue);
+ } catch (\Exception $e) {
+ throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e);
+ }
+ } elseif (\is_string($value)) {
+ // Wrap "N;" in a closure to not confuse it with an encoded `null`
+ if ('N;' === $value) {
+ $isStaticValue = false;
+ }
+ $value = var_export($value, true);
+ } elseif (!is_scalar($value)) {
+ throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \gettype($value)));
+ } else {
+ $value = var_export($value, true);
+ }
+
+ if (!$isStaticValue) {
+ $value = str_replace("\n", "\n ", $value);
+ $value = "static function () {\n return {$value};\n}";
+ }
+ $hash = hash('md5', $value);
+
+ if (null === $id = $dumpedMap[$hash] ?? null) {
+ $id = $dumpedMap[$hash] = \count($dumpedMap);
+ $dumpedValues .= "{$id} => {$value},\n";
+ }
+
+ $dump .= var_export($key, true)." => {$id},\n";
+ }
+
+ $dump .= "\n], [\n\n{$dumpedValues}\n]];\n";
+
+ $tmpFile = uniqid($this->file, true);
+
+ file_put_contents($tmpFile, $dump);
+ @chmod($tmpFile, 0666 & ~umask());
+ unset($serialized, $value, $dump);
+
+ @rename($tmpFile, $this->file);
+ unset(self::$valuesCache[$this->file]);
+
+ $this->initialize();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $prefix
+ *
+ * @return bool
+ */
+ public function clear(/*string $prefix = ''*/)
+ {
+ $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
+ $this->keys = $this->values = [];
+
+ $cleared = @unlink($this->file) || !file_exists($this->file);
+ unset(self::$valuesCache[$this->file]);
+
+ if ($this->pool instanceof AdapterInterface) {
+ return $this->pool->clear($prefix) && $cleared;
+ }
+
+ return $this->pool->clear() && $cleared;
+ }
+
+ /**
+ * Load the cache file.
+ */
+ private function initialize()
+ {
+ if (isset(self::$valuesCache[$this->file])) {
+ $values = self::$valuesCache[$this->file];
+ } elseif (!file_exists($this->file)) {
+ $this->keys = $this->values = [];
+
+ return;
+ } else {
+ $values = self::$valuesCache[$this->file] = (include $this->file) ?: [[], []];
+ }
+
+ if (2 !== \count($values) || !isset($values[0], $values[1])) {
+ $this->keys = $this->values = [];
+ } else {
+ list($this->keys, $this->values) = $values;
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/Traits/PhpFilesTrait.php b/console/skel/symfony/cache/Traits/PhpFilesTrait.php
new file mode 100644
index 0000000..05b9d88
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/PhpFilesTrait.php
@@ -0,0 +1,313 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+use Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\VarExporter\VarExporter;
+
+/**
+ * @author Piotr Stankowski
+ * @author Nicolas Grekas
+ * @author Rob Frawley 2nd
+ *
+ * @internal
+ */
+trait PhpFilesTrait
+{
+ use FilesystemCommonTrait {
+ doClear as private doCommonClear;
+ doDelete as private doCommonDelete;
+ }
+
+ private $includeHandler;
+ private $appendOnly;
+ private $values = [];
+ private $files = [];
+
+ private static $startTime;
+ private static $valuesCache = [];
+
+ public static function isSupported()
+ {
+ self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
+
+ return \function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(ini_get('opcache.enable_cli'), FILTER_VALIDATE_BOOLEAN));
+ }
+
+ /**
+ * @return bool
+ */
+ public function prune()
+ {
+ $time = time();
+ $pruned = true;
+ $getExpiry = true;
+
+ set_error_handler($this->includeHandler);
+ try {
+ foreach ($this->scanHashDir($this->directory) as $file) {
+ try {
+ if (\is_array($expiresAt = include $file)) {
+ $expiresAt = $expiresAt[0];
+ }
+ } catch (\ErrorException $e) {
+ $expiresAt = $time;
+ }
+
+ if ($time >= $expiresAt) {
+ $pruned = $this->doUnlink($file) && !file_exists($file) && $pruned;
+ }
+ }
+ } finally {
+ restore_error_handler();
+ }
+
+ return $pruned;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ if ($this->appendOnly) {
+ $now = 0;
+ $missingIds = [];
+ } else {
+ $now = time();
+ $missingIds = $ids;
+ $ids = [];
+ }
+ $values = [];
+
+ begin:
+ $getExpiry = false;
+
+ foreach ($ids as $id) {
+ if (null === $value = $this->values[$id] ?? null) {
+ $missingIds[] = $id;
+ } elseif ('N;' === $value) {
+ $values[$id] = null;
+ } elseif (!\is_object($value)) {
+ $values[$id] = $value;
+ } elseif (!$value instanceof LazyValue) {
+ $values[$id] = $value();
+ } elseif (false === $values[$id] = include $value->file) {
+ unset($values[$id], $this->values[$id]);
+ $missingIds[] = $id;
+ }
+ if (!$this->appendOnly) {
+ unset($this->values[$id]);
+ }
+ }
+
+ if (!$missingIds) {
+ return $values;
+ }
+
+ set_error_handler($this->includeHandler);
+ try {
+ $getExpiry = true;
+
+ foreach ($missingIds as $k => $id) {
+ try {
+ $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
+
+ if (isset(self::$valuesCache[$file])) {
+ [$expiresAt, $this->values[$id]] = self::$valuesCache[$file];
+ } elseif (\is_array($expiresAt = include $file)) {
+ if ($this->appendOnly) {
+ self::$valuesCache[$file] = $expiresAt;
+ }
+
+ [$expiresAt, $this->values[$id]] = $expiresAt;
+ } elseif ($now < $expiresAt) {
+ $this->values[$id] = new LazyValue($file);
+ }
+
+ if ($now >= $expiresAt) {
+ unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]);
+ }
+ } catch (\ErrorException $e) {
+ unset($missingIds[$k]);
+ }
+ }
+ } finally {
+ restore_error_handler();
+ }
+
+ $ids = $missingIds;
+ $missingIds = [];
+ goto begin;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave($id)
+ {
+ if ($this->appendOnly && isset($this->values[$id])) {
+ return true;
+ }
+
+ set_error_handler($this->includeHandler);
+ try {
+ $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
+ $getExpiry = true;
+
+ if (isset(self::$valuesCache[$file])) {
+ [$expiresAt, $value] = self::$valuesCache[$file];
+ } elseif (\is_array($expiresAt = include $file)) {
+ if ($this->appendOnly) {
+ self::$valuesCache[$file] = $expiresAt;
+ }
+
+ [$expiresAt, $value] = $expiresAt;
+ } elseif ($this->appendOnly) {
+ $value = new LazyValue($file);
+ }
+ } catch (\ErrorException $e) {
+ return false;
+ } finally {
+ restore_error_handler();
+ }
+ if ($this->appendOnly) {
+ $now = 0;
+ $this->values[$id] = $value;
+ } else {
+ $now = time();
+ }
+
+ return $now < $expiresAt;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, $lifetime)
+ {
+ $ok = true;
+ $expiry = $lifetime ? time() + $lifetime : 'PHP_INT_MAX';
+ $allowCompile = self::isSupported();
+
+ foreach ($values as $key => $value) {
+ unset($this->values[$key]);
+ $isStaticValue = true;
+ if (null === $value) {
+ $value = "'N;'";
+ } elseif (\is_object($value) || \is_array($value)) {
+ try {
+ $value = VarExporter::export($value, $isStaticValue);
+ } catch (\Exception $e) {
+ throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e);
+ }
+ } elseif (\is_string($value)) {
+ // Wrap "N;" in a closure to not confuse it with an encoded `null`
+ if ('N;' === $value) {
+ $isStaticValue = false;
+ }
+ $value = var_export($value, true);
+ } elseif (!is_scalar($value)) {
+ throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \gettype($value)));
+ } else {
+ $value = var_export($value, true);
+ }
+
+ $encodedKey = rawurlencode($key);
+
+ if ($isStaticValue) {
+ $value = "return [{$expiry}, {$value}];";
+ } elseif ($this->appendOnly) {
+ $value = "return [{$expiry}, static function () { return {$value}; }];";
+ } else {
+ // We cannot use a closure here because of https://bugs.php.net/76982
+ $value = str_replace('\Symfony\Component\VarExporter\Internal\\', '', $value);
+ $value = "namespace Symfony\Component\VarExporter\Internal;\n\nreturn \$getExpiry ? {$expiry} : {$value};";
+ }
+
+ $file = $this->files[$key] = $this->getFile($key, true);
+ // Since OPcache only compiles files older than the script execution start, set the file's mtime in the past
+ $ok = $this->write($file, "directory)) {
+ throw new CacheException(sprintf('Cache directory is not writable (%s)', $this->directory));
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear($namespace)
+ {
+ $this->values = [];
+
+ return $this->doCommonClear($namespace);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ foreach ($ids as $id) {
+ unset($this->values[$id]);
+ }
+
+ return $this->doCommonDelete($ids);
+ }
+
+ protected function doUnlink($file)
+ {
+ unset(self::$valuesCache[$file]);
+
+ if (self::isSupported()) {
+ @opcache_invalidate($file, true);
+ }
+
+ return @unlink($file);
+ }
+
+ private function getFileKey(string $file): string
+ {
+ if (!$h = @fopen($file, 'rb')) {
+ return '';
+ }
+
+ $encodedKey = substr(fgets($h), 8);
+ fclose($h);
+
+ return rawurldecode(rtrim($encodedKey));
+ }
+}
+
+/**
+ * @internal
+ */
+class LazyValue
+{
+ public $file;
+
+ public function __construct(string $file)
+ {
+ $this->file = $file;
+ }
+}
diff --git a/console/skel/symfony/cache/Traits/ProxyTrait.php b/console/skel/symfony/cache/Traits/ProxyTrait.php
new file mode 100644
index 0000000..c86f360
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/ProxyTrait.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait ProxyTrait
+{
+ private $pool;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prune()
+ {
+ return $this->pool instanceof PruneableInterface && $this->pool->prune();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ if ($this->pool instanceof ResetInterface) {
+ $this->pool->reset();
+ }
+ }
+}
diff --git a/console/skel/symfony/cache/Traits/RedisClusterProxy.php b/console/skel/symfony/cache/Traits/RedisClusterProxy.php
new file mode 100644
index 0000000..b4cef59
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/RedisClusterProxy.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+/**
+ * @author Alessandro Chitolina
+ *
+ * @internal
+ */
+class RedisClusterProxy
+{
+ private $redis;
+ private $initializer;
+
+ public function __construct(\Closure $initializer)
+ {
+ $this->initializer = $initializer;
+ }
+
+ public function __call($method, array $args)
+ {
+ $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+ return $this->redis->{$method}(...$args);
+ }
+
+ public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+ return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount);
+ }
+
+ public function scan(&$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+ return $this->redis->scan($iIterator, $strPattern, $iCount);
+ }
+
+ public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+ return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount);
+ }
+
+ public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+ return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount);
+ }
+}
diff --git a/console/skel/symfony/cache/Traits/RedisProxy.php b/console/skel/symfony/cache/Traits/RedisProxy.php
new file mode 100644
index 0000000..2b0b857
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/RedisProxy.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+class RedisProxy
+{
+ private $redis;
+ private $initializer;
+ private $ready = false;
+
+ public function __construct(\Redis $redis, \Closure $initializer)
+ {
+ $this->redis = $redis;
+ $this->initializer = $initializer;
+ }
+
+ public function __call($method, array $args)
+ {
+ $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
+
+ return $this->redis->{$method}(...$args);
+ }
+
+ public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
+
+ return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount);
+ }
+
+ public function scan(&$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
+
+ return $this->redis->scan($iIterator, $strPattern, $iCount);
+ }
+
+ public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
+
+ return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount);
+ }
+
+ public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
+
+ return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount);
+ }
+}
diff --git a/console/skel/symfony/cache/Traits/RedisTrait.php b/console/skel/symfony/cache/Traits/RedisTrait.php
new file mode 100644
index 0000000..9b65ccb
--- /dev/null
+++ b/console/skel/symfony/cache/Traits/RedisTrait.php
@@ -0,0 +1,511 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+use Predis\Connection\Aggregate\ClusterInterface;
+use Predis\Connection\Aggregate\RedisCluster;
+use Predis\Response\Status;
+use Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+
+/**
+ * @author Aurimas Niekis
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait RedisTrait
+{
+ private static $defaultConnectionOptions = [
+ 'class' => null,
+ 'persistent' => 0,
+ 'persistent_id' => null,
+ 'timeout' => 30,
+ 'read_timeout' => 0,
+ 'retry_interval' => 0,
+ 'tcp_keepalive' => 0,
+ 'lazy' => null,
+ 'redis_cluster' => false,
+ 'redis_sentinel' => null,
+ 'dbindex' => 0,
+ 'failover' => 'none',
+ ];
+ private $redis;
+ private $marshaller;
+
+ /**
+ * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient
+ */
+ private function init($redisClient, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller)
+ {
+ parent::__construct($namespace, $defaultLifetime);
+
+ if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) {
+ throw new InvalidArgumentException(sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
+ }
+
+ if (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \RedisCluster && !$redisClient instanceof \Predis\ClientInterface && !$redisClient instanceof RedisProxy && !$redisClient instanceof RedisClusterProxy) {
+ throw new InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, %s given.', __METHOD__, \is_object($redisClient) ? \get_class($redisClient) : \gettype($redisClient)));
+ }
+
+ if ($redisClient instanceof \Predis\ClientInterface && $redisClient->getOptions()->exceptions) {
+ $options = clone $redisClient->getOptions();
+ \Closure::bind(function () { $this->options['exceptions'] = false; }, $options, $options)();
+ $redisClient = new $redisClient($redisClient->getConnection(), $options);
+ }
+
+ $this->redis = $redisClient;
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+ }
+
+ /**
+ * Creates a Redis connection using a DSN configuration.
+ *
+ * Example DSN:
+ * - redis://localhost
+ * - redis://example.com:1234
+ * - redis://secret@example.com/13
+ * - redis:///var/run/redis.sock
+ * - redis://secret@/var/run/redis.sock/13
+ *
+ * @param string $dsn
+ * @param array $options See self::$defaultConnectionOptions
+ *
+ * @throws InvalidArgumentException when the DSN is invalid
+ *
+ * @return \Redis|\RedisCluster|\Predis\ClientInterface According to the "class" option
+ */
+ public static function createConnection($dsn, array $options = [])
+ {
+ if (0 === strpos($dsn, 'redis:')) {
+ $scheme = 'redis';
+ } elseif (0 === strpos($dsn, 'rediss:')) {
+ $scheme = 'rediss';
+ } else {
+ throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s does not start with "redis:" or "rediss".', $dsn));
+ }
+
+ if (!\extension_loaded('redis') && !class_exists(\Predis\Client::class)) {
+ throw new CacheException(sprintf('Cannot find the "redis" extension nor the "predis/predis" package: %s', $dsn));
+ }
+
+ $params = preg_replace_callback('#^'.$scheme.':(//)?(?:(?:[^:@]*+:)?([^@]*+)@)?#', function ($m) use (&$auth) {
+ if (isset($m[2])) {
+ $auth = $m[2];
+ }
+
+ return 'file:'.($m[1] ?? '');
+ }, $dsn);
+
+ if (false === $params = parse_url($params)) {
+ throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
+ }
+
+ $query = $hosts = [];
+
+ if (isset($params['query'])) {
+ parse_str($params['query'], $query);
+
+ if (isset($query['host'])) {
+ if (!\is_array($hosts = $query['host'])) {
+ throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
+ }
+ foreach ($hosts as $host => $parameters) {
+ if (\is_string($parameters)) {
+ parse_str($parameters, $parameters);
+ }
+ if (false === $i = strrpos($host, ':')) {
+ $hosts[$host] = ['scheme' => 'tcp', 'host' => $host, 'port' => 6379] + $parameters;
+ } elseif ($port = (int) substr($host, 1 + $i)) {
+ $hosts[$host] = ['scheme' => 'tcp', 'host' => substr($host, 0, $i), 'port' => $port] + $parameters;
+ } else {
+ $hosts[$host] = ['scheme' => 'unix', 'path' => substr($host, 0, $i)] + $parameters;
+ }
+ }
+ $hosts = array_values($hosts);
+ }
+ }
+
+ if (isset($params['host']) || isset($params['path'])) {
+ if (!isset($params['dbindex']) && isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
+ $params['dbindex'] = $m[1];
+ $params['path'] = substr($params['path'], 0, -\strlen($m[0]));
+ }
+
+ if (isset($params['host'])) {
+ array_unshift($hosts, ['scheme' => 'tcp', 'host' => $params['host'], 'port' => $params['port'] ?? 6379]);
+ } else {
+ array_unshift($hosts, ['scheme' => 'unix', 'path' => $params['path']]);
+ }
+ }
+
+ if (!$hosts) {
+ throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
+ }
+
+ if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class)) {
+ throw new CacheException(sprintf('Redis Sentinel support requires the "predis/predis" package: %s', $dsn));
+ }
+
+ $params += $query + $options + self::$defaultConnectionOptions;
+
+ if (null === $params['class'] && !isset($params['redis_sentinel']) && \extension_loaded('redis')) {
+ $class = $params['redis_cluster'] ? \RedisCluster::class : (1 < \count($hosts) ? \RedisArray::class : \Redis::class);
+ } else {
+ $class = null === $params['class'] ? \Predis\Client::class : $params['class'];
+ }
+
+ if (is_a($class, \Redis::class, true)) {
+ $connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect';
+ $redis = new $class();
+
+ $initializer = function ($redis) use ($connect, $params, $dsn, $auth, $hosts) {
+ try {
+ @$redis->{$connect}($hosts[0]['host'] ?? $hosts[0]['path'], $hosts[0]['port'] ?? null, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval']);
+ } catch (\RedisException $e) {
+ throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e->getMessage(), $dsn));
+ }
+
+ set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
+ $isConnected = $redis->isConnected();
+ restore_error_handler();
+ if (!$isConnected) {
+ $error = preg_match('/^Redis::p?connect\(\): (.*)/', $error, $error) ? sprintf(' (%s)', $error[1]) : '';
+ throw new InvalidArgumentException(sprintf('Redis connection failed%s: %s', $error, $dsn));
+ }
+
+ if ((null !== $auth && !$redis->auth($auth))
+ || ($params['dbindex'] && !$redis->select($params['dbindex']))
+ || ($params['read_timeout'] && !$redis->setOption(\Redis::OPT_READ_TIMEOUT, $params['read_timeout']))
+ ) {
+ $e = preg_replace('/^ERR /', '', $redis->getLastError());
+ throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e, $dsn));
+ }
+
+ if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
+ $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
+ }
+
+ return true;
+ };
+
+ if ($params['lazy']) {
+ $redis = new RedisProxy($redis, $initializer);
+ } else {
+ $initializer($redis);
+ }
+ } elseif (is_a($class, \RedisArray::class, true)) {
+ foreach ($hosts as $i => $host) {
+ $hosts[$i] = 'tcp' === $host['scheme'] ? $host['host'].':'.$host['port'] : $host['path'];
+ }
+ $params['lazy_connect'] = $params['lazy'] ?? true;
+ $params['connect_timeout'] = $params['timeout'];
+
+ try {
+ $redis = new $class($hosts, $params);
+ } catch (\RedisClusterException $e) {
+ throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e->getMessage(), $dsn));
+ }
+
+ if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
+ $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
+ }
+ } elseif (is_a($class, \RedisCluster::class, true)) {
+ $initializer = function () use ($class, $params, $dsn, $hosts) {
+ foreach ($hosts as $i => $host) {
+ $hosts[$i] = 'tcp' === $host['scheme'] ? $host['host'].':'.$host['port'] : $host['path'];
+ }
+
+ try {
+ $redis = new $class(null, $hosts, $params['timeout'], $params['read_timeout'], (bool) $params['persistent']);
+ } catch (\RedisClusterException $e) {
+ throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e->getMessage(), $dsn));
+ }
+
+ if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
+ $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
+ }
+ switch ($params['failover']) {
+ case 'error': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_ERROR); break;
+ case 'distribute': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE); break;
+ case 'slaves': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES); break;
+ }
+
+ return $redis;
+ };
+
+ $redis = $params['lazy'] ? new RedisClusterProxy($initializer) : $initializer();
+ } elseif (is_a($class, \Predis\ClientInterface::class, true)) {
+ if ($params['redis_cluster']) {
+ $params['cluster'] = 'redis';
+ if (isset($params['redis_sentinel'])) {
+ throw new InvalidArgumentException(sprintf('Cannot use both "redis_cluster" and "redis_sentinel" at the same time: %s', $dsn));
+ }
+ } elseif (isset($params['redis_sentinel'])) {
+ $params['replication'] = 'sentinel';
+ $params['service'] = $params['redis_sentinel'];
+ }
+ $params += ['parameters' => []];
+ $params['parameters'] += [
+ 'persistent' => $params['persistent'],
+ 'timeout' => $params['timeout'],
+ 'read_write_timeout' => $params['read_timeout'],
+ 'tcp_nodelay' => true,
+ ];
+ if ($params['dbindex']) {
+ $params['parameters']['database'] = $params['dbindex'];
+ }
+ if (null !== $auth) {
+ $params['parameters']['password'] = $auth;
+ }
+ if (1 === \count($hosts) && !($params['redis_cluster'] || $params['redis_sentinel'])) {
+ $hosts = $hosts[0];
+ } elseif (\in_array($params['failover'], ['slaves', 'distribute'], true) && !isset($params['replication'])) {
+ $params['replication'] = true;
+ $hosts[0] += ['alias' => 'master'];
+ }
+ $params['exceptions'] = false;
+
+ $redis = new $class($hosts, array_diff_key($params, self::$defaultConnectionOptions));
+ if (isset($params['redis_sentinel'])) {
+ $redis->getConnection()->setSentinelTimeout($params['timeout']);
+ }
+ } elseif (class_exists($class, false)) {
+ throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster" nor "Predis\ClientInterface".', $class));
+ } else {
+ throw new InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
+ }
+
+ return $redis;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ if (!$ids) {
+ return [];
+ }
+
+ $result = [];
+
+ if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) {
+ $values = $this->pipeline(function () use ($ids) {
+ foreach ($ids as $id) {
+ yield 'get' => [$id];
+ }
+ });
+ } else {
+ $values = $this->redis->mget($ids);
+
+ if (!\is_array($values) || \count($values) !== \count($ids)) {
+ return [];
+ }
+
+ $values = array_combine($ids, $values);
+ }
+
+ foreach ($values as $id => $v) {
+ if ($v) {
+ $result[$id] = $this->marshaller->unmarshall($v);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave($id)
+ {
+ return (bool) $this->redis->exists($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear($namespace)
+ {
+ $cleared = true;
+ if ($this->redis instanceof \Predis\ClientInterface) {
+ $evalArgs = [0, $namespace];
+ } else {
+ $evalArgs = [[$namespace], 0];
+ }
+
+ foreach ($this->getHosts() as $host) {
+ if (!isset($namespace[0])) {
+ $cleared = $host->flushDb() && $cleared;
+ continue;
+ }
+
+ $info = $host->info('Server');
+ $info = isset($info['Server']) ? $info['Server'] : $info;
+
+ if (!version_compare($info['redis_version'], '2.8', '>=')) {
+ // As documented in Redis documentation (http://redis.io/commands/keys) using KEYS
+ // can hang your server when it is executed against large databases (millions of items).
+ // Whenever you hit this scale, you should really consider upgrading to Redis 2.8 or above.
+ $cleared = $host->eval("local keys=redis.call('KEYS',ARGV[1]..'*') for i=1,#keys,5000 do redis.call('DEL',unpack(keys,i,math.min(i+4999,#keys))) end return 1", $evalArgs[0], $evalArgs[1]) && $cleared;
+ continue;
+ }
+
+ $cursor = null;
+ do {
+ $keys = $host instanceof \Predis\ClientInterface ? $host->scan($cursor, 'MATCH', $namespace.'*', 'COUNT', 1000) : $host->scan($cursor, $namespace.'*', 1000);
+ if (isset($keys[1]) && \is_array($keys[1])) {
+ $cursor = $keys[0];
+ $keys = $keys[1];
+ }
+ if ($keys) {
+ $this->doDelete($keys);
+ }
+ } while ($cursor = (int) $cursor);
+ }
+
+ return $cleared;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ if (!$ids) {
+ return true;
+ }
+
+ if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) {
+ $this->pipeline(function () use ($ids) {
+ foreach ($ids as $id) {
+ yield 'del' => [$id];
+ }
+ })->rewind();
+ } else {
+ $this->redis->del($ids);
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, $lifetime)
+ {
+ if (!$values = $this->marshaller->marshall($values, $failed)) {
+ return $failed;
+ }
+
+ $results = $this->pipeline(function () use ($values, $lifetime) {
+ foreach ($values as $id => $value) {
+ if (0 >= $lifetime) {
+ yield 'set' => [$id, $value];
+ } else {
+ yield 'setEx' => [$id, $lifetime, $value];
+ }
+ }
+ });
+
+ foreach ($results as $id => $result) {
+ if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) {
+ $failed[] = $id;
+ }
+ }
+
+ return $failed;
+ }
+
+ private function pipeline(\Closure $generator, $redis = null): \Generator
+ {
+ $ids = [];
+ $redis = $redis ?? $this->redis;
+
+ if ($redis instanceof RedisClusterProxy || $redis instanceof \RedisCluster || ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof RedisCluster)) {
+ // phpredis & predis don't support pipelining with RedisCluster
+ // see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining
+ // see https://github.com/nrk/predis/issues/267#issuecomment-123781423
+ $results = [];
+ foreach ($generator() as $command => $args) {
+ $results[] = $redis->{$command}(...$args);
+ $ids[] = 'eval' === $command ? ($redis instanceof \Predis\ClientInterface ? $args[2] : $args[1][0]) : $args[0];
+ }
+ } elseif ($redis instanceof \Predis\ClientInterface) {
+ $results = $redis->pipeline(static function ($redis) use ($generator, &$ids) {
+ foreach ($generator() as $command => $args) {
+ $redis->{$command}(...$args);
+ $ids[] = 'eval' === $command ? $args[2] : $args[0];
+ }
+ });
+ } elseif ($redis instanceof \RedisArray) {
+ $connections = $results = $ids = [];
+ foreach ($generator() as $command => $args) {
+ $id = 'eval' === $command ? $args[1][0] : $args[0];
+ if (!isset($connections[$h = $redis->_target($id)])) {
+ $connections[$h] = [$redis->_instance($h), -1];
+ $connections[$h][0]->multi(\Redis::PIPELINE);
+ }
+ $connections[$h][0]->{$command}(...$args);
+ $results[] = [$h, ++$connections[$h][1]];
+ $ids[] = $id;
+ }
+ foreach ($connections as $h => $c) {
+ $connections[$h] = $c[0]->exec();
+ }
+ foreach ($results as $k => list($h, $c)) {
+ $results[$k] = $connections[$h][$c];
+ }
+ } else {
+ $redis->multi(\Redis::PIPELINE);
+ foreach ($generator() as $command => $args) {
+ $redis->{$command}(...$args);
+ $ids[] = 'eval' === $command ? $args[1][0] : $args[0];
+ }
+ $results = $redis->exec();
+ }
+
+ foreach ($ids as $k => $id) {
+ yield $id => $results[$k];
+ }
+ }
+
+ private function getHosts(): array
+ {
+ $hosts = [$this->redis];
+ if ($this->redis instanceof \Predis\ClientInterface) {
+ $connection = $this->redis->getConnection();
+ if ($connection instanceof ClusterInterface && $connection instanceof \Traversable) {
+ $hosts = [];
+ foreach ($connection as $c) {
+ $hosts[] = new \Predis\Client($c);
+ }
+ }
+ } elseif ($this->redis instanceof \RedisArray) {
+ $hosts = [];
+ foreach ($this->redis->_hosts() as $host) {
+ $hosts[] = $this->redis->_instance($host);
+ }
+ } elseif ($this->redis instanceof RedisClusterProxy || $this->redis instanceof \RedisCluster) {
+ $hosts = [];
+ foreach ($this->redis->_masters() as $host) {
+ $hosts[] = $h = new \Redis();
+ $h->connect($host[0], $host[1]);
+ }
+ }
+
+ return $hosts;
+ }
+}
diff --git a/console/skel/symfony/cache/composer.json b/console/skel/symfony/cache/composer.json
new file mode 100644
index 0000000..0f0033c
--- /dev/null
+++ b/console/skel/symfony/cache/composer.json
@@ -0,0 +1,59 @@
+{
+ "name": "symfony/cache",
+ "type": "library",
+ "description": "Symfony Cache component with PSR-6, PSR-16, and tags",
+ "keywords": ["caching", "psr6"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "provide": {
+ "psr/cache-implementation": "1.0",
+ "psr/simple-cache-implementation": "1.0",
+ "symfony/cache-implementation": "1.0"
+ },
+ "require": {
+ "php": "^7.1.3",
+ "psr/cache": "~1.0",
+ "psr/log": "~1.0",
+ "symfony/cache-contracts": "^1.1.7|^2",
+ "symfony/service-contracts": "^1.1|^2",
+ "symfony/var-exporter": "^4.2|^5.0"
+ },
+ "require-dev": {
+ "cache/integration-tests": "dev-master",
+ "doctrine/cache": "~1.6",
+ "doctrine/dbal": "~2.5",
+ "predis/predis": "~1.1",
+ "psr/simple-cache": "^1.0",
+ "symfony/config": "^4.2|^5.0",
+ "symfony/dependency-injection": "^3.4|^4.1|^5.0",
+ "symfony/var-dumper": "^4.4|^5.0"
+ },
+ "conflict": {
+ "doctrine/dbal": "<2.5",
+ "symfony/dependency-injection": "<3.4",
+ "symfony/http-kernel": "<4.4",
+ "symfony/var-dumper": "<4.4"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Cache\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.4-dev"
+ }
+ }
+}
diff --git a/console/skel/symfony/config/.gitignore b/console/skel/symfony/config/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/console/skel/symfony/config/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/console/skel/symfony/config/CHANGELOG.md b/console/skel/symfony/config/CHANGELOG.md
new file mode 100644
index 0000000..4ef4e62
--- /dev/null
+++ b/console/skel/symfony/config/CHANGELOG.md
@@ -0,0 +1,71 @@
+CHANGELOG
+=========
+
+3.3.0
+-----
+
+ * added `ReflectionClassResource` class
+ * added second `$exists` constructor argument to `ClassExistenceResource`
+ * made `ClassExistenceResource` work with interfaces and traits
+ * added `ConfigCachePass` (originally in FrameworkBundle)
+ * added `castToArray()` helper to turn any config value into an array
+
+3.0.0
+-----
+
+ * removed `ReferenceDumper` class
+ * removed the `ResourceInterface::isFresh()` method
+ * removed `BCResourceInterfaceChecker` class
+ * removed `ResourceInterface::getResource()` method
+
+2.8.0
+-----
+
+The edge case of defining just one value for nodes of type Enum is now allowed:
+
+```php
+$rootNode
+ ->children()
+ ->enumNode('variable')
+ ->values(array('value'))
+ ->end()
+ ->end()
+;
+```
+
+Before: `InvalidArgumentException` (variable must contain at least two
+distinct elements).
+After: the code will work as expected and it will restrict the values of the
+`variable` option to just `value`.
+
+ * deprecated the `ResourceInterface::isFresh()` method. If you implement custom resource types and they
+ can be validated that way, make them implement the new `SelfCheckingResourceInterface`.
+ * deprecated the getResource() method in ResourceInterface. You can still call this method
+ on concrete classes implementing the interface, but it does not make sense at the interface
+ level as you need to know about the particular type of resource at hand to understand the
+ semantics of the returned value.
+
+2.7.0
+-----
+
+ * added `ConfigCacheInterface`, `ConfigCacheFactoryInterface` and a basic `ConfigCacheFactory`
+ implementation to delegate creation of ConfigCache instances
+
+2.2.0
+-----
+
+ * added `ArrayNodeDefinition::canBeEnabled()` and `ArrayNodeDefinition::canBeDisabled()`
+ to ease configuration when some sections are respectively disabled / enabled
+ by default.
+ * added a `normalizeKeys()` method for array nodes (to avoid key normalization)
+ * added numerical type handling for config definitions
+ * added convenience methods for optional configuration sections to `ArrayNodeDefinition`
+ * added a utils class for XML manipulations
+
+2.1.0
+-----
+
+ * added a way to add documentation on configuration
+ * implemented `Serializable` on resources
+ * `LoaderResolverInterface` is now used instead of `LoaderResolver` for type
+ hinting
diff --git a/console/skel/symfony/config/ConfigCache.php b/console/skel/symfony/config/ConfigCache.php
new file mode 100644
index 0000000..591c89b
--- /dev/null
+++ b/console/skel/symfony/config/ConfigCache.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+use Symfony\Component\Config\Resource\SelfCheckingResourceChecker;
+
+/**
+ * ConfigCache caches arbitrary content in files on disk.
+ *
+ * When in debug mode, those metadata resources that implement
+ * \Symfony\Component\Config\Resource\SelfCheckingResourceInterface will
+ * be used to check cache freshness.
+ *
+ * @author Fabien Potencier
+ * @author Matthias Pigulla
+ */
+class ConfigCache extends ResourceCheckerConfigCache
+{
+ private $debug;
+
+ /**
+ * @param string $file The absolute cache path
+ * @param bool $debug Whether debugging is enabled or not
+ */
+ public function __construct($file, $debug)
+ {
+ $this->debug = (bool) $debug;
+
+ $checkers = array();
+ if (true === $this->debug) {
+ $checkers = array(new SelfCheckingResourceChecker());
+ }
+
+ parent::__construct($file, $checkers);
+ }
+
+ /**
+ * Checks if the cache is still fresh.
+ *
+ * This implementation always returns true when debug is off and the
+ * cache file exists.
+ *
+ * @return bool true if the cache is fresh, false otherwise
+ */
+ public function isFresh()
+ {
+ if (!$this->debug && is_file($this->getPath())) {
+ return true;
+ }
+
+ return parent::isFresh();
+ }
+}
diff --git a/console/skel/symfony/config/ConfigCacheFactory.php b/console/skel/symfony/config/ConfigCacheFactory.php
new file mode 100644
index 0000000..396536e
--- /dev/null
+++ b/console/skel/symfony/config/ConfigCacheFactory.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+/**
+ * Basic implementation of ConfigCacheFactoryInterface that
+ * creates an instance of the default ConfigCache.
+ *
+ * This factory and/or cache do not support cache validation
+ * by means of ResourceChecker instances (that is, service-based).
+ *
+ * @author Matthias Pigulla
+ */
+class ConfigCacheFactory implements ConfigCacheFactoryInterface
+{
+ /**
+ * @var bool Debug flag passed to the ConfigCache
+ */
+ private $debug;
+
+ /**
+ * @param bool $debug The debug flag to pass to ConfigCache
+ */
+ public function __construct($debug)
+ {
+ $this->debug = $debug;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cache($file, $callback)
+ {
+ if (!is_callable($callback)) {
+ throw new \InvalidArgumentException(sprintf('Invalid type for callback argument. Expected callable, but got "%s".', gettype($callback)));
+ }
+
+ $cache = new ConfigCache($file, $this->debug);
+ if (!$cache->isFresh()) {
+ call_user_func($callback, $cache);
+ }
+
+ return $cache;
+ }
+}
diff --git a/console/skel/symfony/config/ConfigCacheFactoryInterface.php b/console/skel/symfony/config/ConfigCacheFactoryInterface.php
new file mode 100644
index 0000000..bd614c4
--- /dev/null
+++ b/console/skel/symfony/config/ConfigCacheFactoryInterface.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+/**
+ * Interface for a ConfigCache factory. This factory creates
+ * an instance of ConfigCacheInterface and initializes the
+ * cache if necessary.
+ *
+ * @author Matthias Pigulla
+ */
+interface ConfigCacheFactoryInterface
+{
+ /**
+ * Creates a cache instance and (re-)initializes it if necessary.
+ *
+ * @param string $file The absolute cache file path
+ * @param callable $callable The callable to be executed when the cache needs to be filled (i. e. is not fresh). The cache will be passed as the only parameter to this callback
+ *
+ * @return ConfigCacheInterface $configCache The cache instance
+ */
+ public function cache($file, $callable);
+}
diff --git a/console/skel/symfony/config/ConfigCacheInterface.php b/console/skel/symfony/config/ConfigCacheInterface.php
new file mode 100644
index 0000000..7c47ad7
--- /dev/null
+++ b/console/skel/symfony/config/ConfigCacheInterface.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+use Symfony\Component\Config\Resource\ResourceInterface;
+
+/**
+ * Interface for ConfigCache.
+ *
+ * @author Matthias Pigulla
+ */
+interface ConfigCacheInterface
+{
+ /**
+ * Gets the cache file path.
+ *
+ * @return string The cache file path
+ */
+ public function getPath();
+
+ /**
+ * Checks if the cache is still fresh.
+ *
+ * This check should take the metadata passed to the write() method into consideration.
+ *
+ * @return bool Whether the cache is still fresh
+ */
+ public function isFresh();
+
+ /**
+ * Writes the given content into the cache file. Metadata will be stored
+ * independently and can be used to check cache freshness at a later time.
+ *
+ * @param string $content The content to write into the cache
+ * @param ResourceInterface[]|null $metadata An array of ResourceInterface instances
+ *
+ * @throws \RuntimeException When the cache file cannot be written
+ */
+ public function write($content, array $metadata = null);
+}
diff --git a/console/skel/symfony/config/Definition/ArrayNode.php b/console/skel/symfony/config/Definition/ArrayNode.php
new file mode 100644
index 0000000..457e7a8
--- /dev/null
+++ b/console/skel/symfony/config/Definition/ArrayNode.php
@@ -0,0 +1,399 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
+use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
+
+/**
+ * Represents an Array node in the config tree.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ArrayNode extends BaseNode implements PrototypeNodeInterface
+{
+ protected $xmlRemappings = array();
+ protected $children = array();
+ protected $allowFalse = false;
+ protected $allowNewKeys = true;
+ protected $addIfNotSet = false;
+ protected $performDeepMerging = true;
+ protected $ignoreExtraKeys = false;
+ protected $removeExtraKeys = true;
+ protected $normalizeKeys = true;
+
+ public function setNormalizeKeys($normalizeKeys)
+ {
+ $this->normalizeKeys = (bool) $normalizeKeys;
+ }
+
+ /**
+ * Normalizes keys between the different configuration formats.
+ *
+ * Namely, you mostly have foo_bar in YAML while you have foo-bar in XML.
+ * After running this method, all keys are normalized to foo_bar.
+ *
+ * If you have a mixed key like foo-bar_moo, it will not be altered.
+ * The key will also not be altered if the target key already exists.
+ *
+ * @param mixed $value
+ *
+ * @return array The value with normalized keys
+ */
+ protected function preNormalize($value)
+ {
+ if (!$this->normalizeKeys || !is_array($value)) {
+ return $value;
+ }
+
+ $normalized = array();
+
+ foreach ($value as $k => $v) {
+ if (false !== strpos($k, '-') && false === strpos($k, '_') && !array_key_exists($normalizedKey = str_replace('-', '_', $k), $value)) {
+ $normalized[$normalizedKey] = $v;
+ } else {
+ $normalized[$k] = $v;
+ }
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * Retrieves the children of this node.
+ *
+ * @return array The children
+ */
+ public function getChildren()
+ {
+ return $this->children;
+ }
+
+ /**
+ * Sets the xml remappings that should be performed.
+ *
+ * @param array $remappings an array of the form array(array(string, string))
+ */
+ public function setXmlRemappings(array $remappings)
+ {
+ $this->xmlRemappings = $remappings;
+ }
+
+ /**
+ * Gets the xml remappings that should be performed.
+ *
+ * @return array $remappings an array of the form array(array(string, string))
+ */
+ public function getXmlRemappings()
+ {
+ return $this->xmlRemappings;
+ }
+
+ /**
+ * Sets whether to add default values for this array if it has not been
+ * defined in any of the configuration files.
+ *
+ * @param bool $boolean
+ */
+ public function setAddIfNotSet($boolean)
+ {
+ $this->addIfNotSet = (bool) $boolean;
+ }
+
+ /**
+ * Sets whether false is allowed as value indicating that the array should be unset.
+ *
+ * @param bool $allow
+ */
+ public function setAllowFalse($allow)
+ {
+ $this->allowFalse = (bool) $allow;
+ }
+
+ /**
+ * Sets whether new keys can be defined in subsequent configurations.
+ *
+ * @param bool $allow
+ */
+ public function setAllowNewKeys($allow)
+ {
+ $this->allowNewKeys = (bool) $allow;
+ }
+
+ /**
+ * Sets if deep merging should occur.
+ *
+ * @param bool $boolean
+ */
+ public function setPerformDeepMerging($boolean)
+ {
+ $this->performDeepMerging = (bool) $boolean;
+ }
+
+ /**
+ * Whether extra keys should just be ignore without an exception.
+ *
+ * @param bool $boolean To allow extra keys
+ * @param bool $remove To remove extra keys
+ */
+ public function setIgnoreExtraKeys($boolean, $remove = true)
+ {
+ $this->ignoreExtraKeys = (bool) $boolean;
+ $this->removeExtraKeys = $this->ignoreExtraKeys && $remove;
+ }
+
+ /**
+ * Sets the node Name.
+ *
+ * @param string $name The node's name
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * Checks if the node has a default value.
+ *
+ * @return bool
+ */
+ public function hasDefaultValue()
+ {
+ return $this->addIfNotSet;
+ }
+
+ /**
+ * Retrieves the default value.
+ *
+ * @return array The default value
+ *
+ * @throws \RuntimeException if the node has no default value
+ */
+ public function getDefaultValue()
+ {
+ if (!$this->hasDefaultValue()) {
+ throw new \RuntimeException(sprintf('The node at path "%s" has no default value.', $this->getPath()));
+ }
+
+ $defaults = array();
+ foreach ($this->children as $name => $child) {
+ if ($child->hasDefaultValue()) {
+ $defaults[$name] = $child->getDefaultValue();
+ }
+ }
+
+ return $defaults;
+ }
+
+ /**
+ * Adds a child node.
+ *
+ * @param NodeInterface $node The child node to add
+ *
+ * @throws \InvalidArgumentException when the child node has no name
+ * @throws \InvalidArgumentException when the child node's name is not unique
+ */
+ public function addChild(NodeInterface $node)
+ {
+ $name = $node->getName();
+ if (!strlen($name)) {
+ throw new \InvalidArgumentException('Child nodes must be named.');
+ }
+ if (isset($this->children[$name])) {
+ throw new \InvalidArgumentException(sprintf('A child node named "%s" already exists.', $name));
+ }
+
+ $this->children[$name] = $node;
+ }
+
+ /**
+ * Finalizes the value of this node.
+ *
+ * @param mixed $value
+ *
+ * @return mixed The finalised value
+ *
+ * @throws UnsetKeyException
+ * @throws InvalidConfigurationException if the node doesn't have enough children
+ */
+ protected function finalizeValue($value)
+ {
+ if (false === $value) {
+ $msg = sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value));
+ throw new UnsetKeyException($msg);
+ }
+
+ foreach ($this->children as $name => $child) {
+ if (!array_key_exists($name, $value)) {
+ if ($child->isRequired()) {
+ $msg = sprintf('The child node "%s" at path "%s" must be configured.', $name, $this->getPath());
+ $ex = new InvalidConfigurationException($msg);
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+
+ if ($child->hasDefaultValue()) {
+ $value[$name] = $child->getDefaultValue();
+ }
+
+ continue;
+ }
+
+ try {
+ $value[$name] = $child->finalize($value[$name]);
+ } catch (UnsetKeyException $e) {
+ unset($value[$name]);
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Validates the type of the value.
+ *
+ * @param mixed $value
+ *
+ * @throws InvalidTypeException
+ */
+ protected function validateType($value)
+ {
+ if (!is_array($value) && (!$this->allowFalse || false !== $value)) {
+ $ex = new InvalidTypeException(sprintf(
+ 'Invalid type for path "%s". Expected array, but got %s',
+ $this->getPath(),
+ gettype($value)
+ ));
+ if ($hint = $this->getInfo()) {
+ $ex->addHint($hint);
+ }
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+ }
+
+ /**
+ * Normalizes the value.
+ *
+ * @param mixed $value The value to normalize
+ *
+ * @return mixed The normalized value
+ *
+ * @throws InvalidConfigurationException
+ */
+ protected function normalizeValue($value)
+ {
+ if (false === $value) {
+ return $value;
+ }
+
+ $value = $this->remapXml($value);
+
+ $normalized = array();
+ foreach ($value as $name => $val) {
+ if (isset($this->children[$name])) {
+ $normalized[$name] = $this->children[$name]->normalize($val);
+ unset($value[$name]);
+ } elseif (!$this->removeExtraKeys) {
+ $normalized[$name] = $val;
+ }
+ }
+
+ // if extra fields are present, throw exception
+ if (count($value) && !$this->ignoreExtraKeys) {
+ $msg = sprintf('Unrecognized option%s "%s" under "%s"', 1 === count($value) ? '' : 's', implode(', ', array_keys($value)), $this->getPath());
+ $ex = new InvalidConfigurationException($msg);
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * Remaps multiple singular values to a single plural value.
+ *
+ * @param array $value The source values
+ *
+ * @return array The remapped values
+ */
+ protected function remapXml($value)
+ {
+ foreach ($this->xmlRemappings as list($singular, $plural)) {
+ if (!isset($value[$singular])) {
+ continue;
+ }
+
+ $value[$plural] = Processor::normalizeConfig($value, $singular, $plural);
+ unset($value[$singular]);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Merges values together.
+ *
+ * @param mixed $leftSide The left side to merge
+ * @param mixed $rightSide The right side to merge
+ *
+ * @return mixed The merged values
+ *
+ * @throws InvalidConfigurationException
+ * @throws \RuntimeException
+ */
+ protected function mergeValues($leftSide, $rightSide)
+ {
+ if (false === $rightSide) {
+ // if this is still false after the last config has been merged the
+ // finalization pass will take care of removing this key entirely
+ return false;
+ }
+
+ if (false === $leftSide || !$this->performDeepMerging) {
+ return $rightSide;
+ }
+
+ foreach ($rightSide as $k => $v) {
+ // no conflict
+ if (!array_key_exists($k, $leftSide)) {
+ if (!$this->allowNewKeys) {
+ $ex = new InvalidConfigurationException(sprintf(
+ 'You are not allowed to define new elements for path "%s". '
+ .'Please define all elements for this path in one config file. '
+ .'If you are trying to overwrite an element, make sure you redefine it '
+ .'with the same name.',
+ $this->getPath()
+ ));
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+
+ $leftSide[$k] = $v;
+ continue;
+ }
+
+ if (!isset($this->children[$k])) {
+ throw new \RuntimeException('merge() expects a normalized config array.');
+ }
+
+ $leftSide[$k] = $this->children[$k]->merge($leftSide[$k], $v);
+ }
+
+ return $leftSide;
+ }
+}
diff --git a/console/skel/symfony/config/Definition/BaseNode.php b/console/skel/symfony/config/Definition/BaseNode.php
new file mode 100644
index 0000000..dbf3633
--- /dev/null
+++ b/console/skel/symfony/config/Definition/BaseNode.php
@@ -0,0 +1,356 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\Exception;
+use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
+
+/**
+ * The base node class.
+ *
+ * @author Johannes M. Schmitt
+ */
+abstract class BaseNode implements NodeInterface
+{
+ protected $name;
+ protected $parent;
+ protected $normalizationClosures = array();
+ protected $finalValidationClosures = array();
+ protected $allowOverwrite = true;
+ protected $required = false;
+ protected $equivalentValues = array();
+ protected $attributes = array();
+
+ /**
+ * Constructor.
+ *
+ * @param string $name The name of the node
+ * @param NodeInterface $parent The parent of this node
+ *
+ * @throws \InvalidArgumentException if the name contains a period.
+ */
+ public function __construct($name, NodeInterface $parent = null)
+ {
+ if (false !== strpos($name, '.')) {
+ throw new \InvalidArgumentException('The name must not contain ".".');
+ }
+
+ $this->name = $name;
+ $this->parent = $parent;
+ }
+
+ public function setAttribute($key, $value)
+ {
+ $this->attributes[$key] = $value;
+ }
+
+ public function getAttribute($key, $default = null)
+ {
+ return isset($this->attributes[$key]) ? $this->attributes[$key] : $default;
+ }
+
+ public function hasAttribute($key)
+ {
+ return isset($this->attributes[$key]);
+ }
+
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ public function setAttributes(array $attributes)
+ {
+ $this->attributes = $attributes;
+ }
+
+ public function removeAttribute($key)
+ {
+ unset($this->attributes[$key]);
+ }
+
+ /**
+ * Sets an info message.
+ *
+ * @param string $info
+ */
+ public function setInfo($info)
+ {
+ $this->setAttribute('info', $info);
+ }
+
+ /**
+ * Returns info message.
+ *
+ * @return string The info text
+ */
+ public function getInfo()
+ {
+ return $this->getAttribute('info');
+ }
+
+ /**
+ * Sets the example configuration for this node.
+ *
+ * @param string|array $example
+ */
+ public function setExample($example)
+ {
+ $this->setAttribute('example', $example);
+ }
+
+ /**
+ * Retrieves the example configuration for this node.
+ *
+ * @return string|array The example
+ */
+ public function getExample()
+ {
+ return $this->getAttribute('example');
+ }
+
+ /**
+ * Adds an equivalent value.
+ *
+ * @param mixed $originalValue
+ * @param mixed $equivalentValue
+ */
+ public function addEquivalentValue($originalValue, $equivalentValue)
+ {
+ $this->equivalentValues[] = array($originalValue, $equivalentValue);
+ }
+
+ /**
+ * Set this node as required.
+ *
+ * @param bool $boolean Required node
+ */
+ public function setRequired($boolean)
+ {
+ $this->required = (bool) $boolean;
+ }
+
+ /**
+ * Sets if this node can be overridden.
+ *
+ * @param bool $allow
+ */
+ public function setAllowOverwrite($allow)
+ {
+ $this->allowOverwrite = (bool) $allow;
+ }
+
+ /**
+ * Sets the closures used for normalization.
+ *
+ * @param \Closure[] $closures An array of Closures used for normalization
+ */
+ public function setNormalizationClosures(array $closures)
+ {
+ $this->normalizationClosures = $closures;
+ }
+
+ /**
+ * Sets the closures used for final validation.
+ *
+ * @param \Closure[] $closures An array of Closures used for final validation
+ */
+ public function setFinalValidationClosures(array $closures)
+ {
+ $this->finalValidationClosures = $closures;
+ }
+
+ /**
+ * Checks if this node is required.
+ *
+ * @return bool
+ */
+ public function isRequired()
+ {
+ return $this->required;
+ }
+
+ /**
+ * Returns the name of this node.
+ *
+ * @return string The Node's name
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Retrieves the path of this node.
+ *
+ * @return string The Node's path
+ */
+ public function getPath()
+ {
+ $path = $this->name;
+
+ if (null !== $this->parent) {
+ $path = $this->parent->getPath().'.'.$path;
+ }
+
+ return $path;
+ }
+
+ /**
+ * Merges two values together.
+ *
+ * @param mixed $leftSide
+ * @param mixed $rightSide
+ *
+ * @return mixed The merged value
+ *
+ * @throws ForbiddenOverwriteException
+ */
+ final public function merge($leftSide, $rightSide)
+ {
+ if (!$this->allowOverwrite) {
+ throw new ForbiddenOverwriteException(sprintf(
+ 'Configuration path "%s" cannot be overwritten. You have to '
+ .'define all options for this path, and any of its sub-paths in '
+ .'one configuration section.',
+ $this->getPath()
+ ));
+ }
+
+ $this->validateType($leftSide);
+ $this->validateType($rightSide);
+
+ return $this->mergeValues($leftSide, $rightSide);
+ }
+
+ /**
+ * Normalizes a value, applying all normalization closures.
+ *
+ * @param mixed $value Value to normalize
+ *
+ * @return mixed The normalized value
+ */
+ final public function normalize($value)
+ {
+ $value = $this->preNormalize($value);
+
+ // run custom normalization closures
+ foreach ($this->normalizationClosures as $closure) {
+ $value = $closure($value);
+ }
+
+ // replace value with their equivalent
+ foreach ($this->equivalentValues as $data) {
+ if ($data[0] === $value) {
+ $value = $data[1];
+ }
+ }
+
+ // validate type
+ $this->validateType($value);
+
+ // normalize value
+ return $this->normalizeValue($value);
+ }
+
+ /**
+ * Normalizes the value before any other normalization is applied.
+ *
+ * @param $value
+ *
+ * @return $value The normalized array value
+ */
+ protected function preNormalize($value)
+ {
+ return $value;
+ }
+
+ /**
+ * Returns parent node for this node.
+ *
+ * @return NodeInterface|null
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Finalizes a value, applying all finalization closures.
+ *
+ * @param mixed $value The value to finalize
+ *
+ * @return mixed The finalized value
+ *
+ * @throws Exception
+ * @throws InvalidConfigurationException
+ */
+ final public function finalize($value)
+ {
+ $this->validateType($value);
+
+ $value = $this->finalizeValue($value);
+
+ // Perform validation on the final value if a closure has been set.
+ // The closure is also allowed to return another value.
+ foreach ($this->finalValidationClosures as $closure) {
+ try {
+ $value = $closure($value);
+ } catch (Exception $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": %s', $this->getPath(), $e->getMessage()), $e->getCode(), $e);
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Validates the type of a Node.
+ *
+ * @param mixed $value The value to validate
+ *
+ * @throws InvalidTypeException when the value is invalid
+ */
+ abstract protected function validateType($value);
+
+ /**
+ * Normalizes the value.
+ *
+ * @param mixed $value The value to normalize
+ *
+ * @return mixed The normalized value
+ */
+ abstract protected function normalizeValue($value);
+
+ /**
+ * Merges two values together.
+ *
+ * @param mixed $leftSide
+ * @param mixed $rightSide
+ *
+ * @return mixed The merged value
+ */
+ abstract protected function mergeValues($leftSide, $rightSide);
+
+ /**
+ * Finalizes a value.
+ *
+ * @param mixed $value The value to finalize
+ *
+ * @return mixed The finalized value
+ */
+ abstract protected function finalizeValue($value);
+}
diff --git a/console/skel/symfony/config/Definition/BooleanNode.php b/console/skel/symfony/config/Definition/BooleanNode.php
new file mode 100644
index 0000000..08e1a77
--- /dev/null
+++ b/console/skel/symfony/config/Definition/BooleanNode.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
+
+/**
+ * This node represents a Boolean value in the config tree.
+ *
+ * @author Johannes M. Schmitt
+ */
+class BooleanNode extends ScalarNode
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function validateType($value)
+ {
+ if (!is_bool($value)) {
+ $ex = new InvalidTypeException(sprintf(
+ 'Invalid type for path "%s". Expected boolean, but got %s.',
+ $this->getPath(),
+ gettype($value)
+ ));
+ if ($hint = $this->getInfo()) {
+ $ex->addHint($hint);
+ }
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function isValueEmpty($value)
+ {
+ // a boolean value cannot be empty
+ return false;
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Builder/ArrayNodeDefinition.php b/console/skel/symfony/config/Definition/Builder/ArrayNodeDefinition.php
new file mode 100644
index 0000000..9db0b36
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/ArrayNodeDefinition.php
@@ -0,0 +1,549 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\ArrayNode;
+use Symfony\Component\Config\Definition\PrototypedArrayNode;
+use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
+
+/**
+ * This class provides a fluent interface for defining an array node.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface
+{
+ protected $performDeepMerging = true;
+ protected $ignoreExtraKeys = false;
+ protected $removeExtraKeys = true;
+ protected $children = array();
+ protected $prototype;
+ protected $atLeastOne = false;
+ protected $allowNewKeys = true;
+ protected $key;
+ protected $removeKeyItem;
+ protected $addDefaults = false;
+ protected $addDefaultChildren = false;
+ protected $nodeBuilder;
+ protected $normalizeKeys = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct($name, NodeParentInterface $parent = null)
+ {
+ parent::__construct($name, $parent);
+
+ $this->nullEquivalent = array();
+ $this->trueEquivalent = array();
+ }
+
+ /**
+ * Sets a custom children builder.
+ *
+ * @param NodeBuilder $builder A custom NodeBuilder
+ */
+ public function setBuilder(NodeBuilder $builder)
+ {
+ $this->nodeBuilder = $builder;
+ }
+
+ /**
+ * Returns a builder to add children nodes.
+ *
+ * @return NodeBuilder
+ */
+ public function children()
+ {
+ return $this->getNodeBuilder();
+ }
+
+ /**
+ * Sets a prototype for child nodes.
+ *
+ * @param string $type the type of node
+ *
+ * @return NodeDefinition
+ */
+ public function prototype($type)
+ {
+ return $this->prototype = $this->getNodeBuilder()->node(null, $type)->setParent($this);
+ }
+
+ /**
+ * @return VariableNodeDefinition
+ */
+ public function variablePrototype()
+ {
+ return $this->prototype('variable');
+ }
+
+ /**
+ * @return ScalarNodeDefinition
+ */
+ public function scalarPrototype()
+ {
+ return $this->prototype('scalar');
+ }
+
+ /**
+ * @return BooleanNodeDefinition
+ */
+ public function booleanPrototype()
+ {
+ return $this->prototype('boolean');
+ }
+
+ /**
+ * @return IntegerNodeDefinition
+ */
+ public function integerPrototype()
+ {
+ return $this->prototype('integer');
+ }
+
+ /**
+ * @return FloatNodeDefinition
+ */
+ public function floatPrototype()
+ {
+ return $this->prototype('float');
+ }
+
+ /**
+ * @return ArrayNodeDefinition
+ */
+ public function arrayPrototype()
+ {
+ return $this->prototype('array');
+ }
+
+ /**
+ * @return EnumNodeDefinition
+ */
+ public function enumPrototype()
+ {
+ return $this->prototype('enum');
+ }
+
+ /**
+ * Adds the default value if the node is not set in the configuration.
+ *
+ * This method is applicable to concrete nodes only (not to prototype nodes).
+ * If this function has been called and the node is not set during the finalization
+ * phase, it's default value will be derived from its children default values.
+ *
+ * @return $this
+ */
+ public function addDefaultsIfNotSet()
+ {
+ $this->addDefaults = true;
+
+ return $this;
+ }
+
+ /**
+ * Adds children with a default value when none are defined.
+ *
+ * @param int|string|array|null $children The number of children|The child name|The children names to be added
+ *
+ * This method is applicable to prototype nodes only.
+ *
+ * @return $this
+ */
+ public function addDefaultChildrenIfNoneSet($children = null)
+ {
+ $this->addDefaultChildren = $children;
+
+ return $this;
+ }
+
+ /**
+ * Requires the node to have at least one element.
+ *
+ * This method is applicable to prototype nodes only.
+ *
+ * @return $this
+ */
+ public function requiresAtLeastOneElement()
+ {
+ $this->atLeastOne = true;
+
+ return $this;
+ }
+
+ /**
+ * Disallows adding news keys in a subsequent configuration.
+ *
+ * If used all keys have to be defined in the same configuration file.
+ *
+ * @return $this
+ */
+ public function disallowNewKeysInSubsequentConfigs()
+ {
+ $this->allowNewKeys = false;
+
+ return $this;
+ }
+
+ /**
+ * Sets a normalization rule for XML configurations.
+ *
+ * @param string $singular The key to remap
+ * @param string $plural The plural of the key for irregular plurals
+ *
+ * @return $this
+ */
+ public function fixXmlConfig($singular, $plural = null)
+ {
+ $this->normalization()->remap($singular, $plural);
+
+ return $this;
+ }
+
+ /**
+ * Sets the attribute which value is to be used as key.
+ *
+ * This is useful when you have an indexed array that should be an
+ * associative array. You can select an item from within the array
+ * to be the key of the particular item. For example, if "id" is the
+ * "key", then:
+ *
+ * array(
+ * array('id' => 'my_name', 'foo' => 'bar'),
+ * );
+ *
+ * becomes
+ *
+ * array(
+ * 'my_name' => array('foo' => 'bar'),
+ * );
+ *
+ * If you'd like "'id' => 'my_name'" to still be present in the resulting
+ * array, then you can set the second argument of this method to false.
+ *
+ * This method is applicable to prototype nodes only.
+ *
+ * @param string $name The name of the key
+ * @param bool $removeKeyItem Whether or not the key item should be removed
+ *
+ * @return $this
+ */
+ public function useAttributeAsKey($name, $removeKeyItem = true)
+ {
+ $this->key = $name;
+ $this->removeKeyItem = $removeKeyItem;
+
+ return $this;
+ }
+
+ /**
+ * Sets whether the node can be unset.
+ *
+ * @param bool $allow
+ *
+ * @return $this
+ */
+ public function canBeUnset($allow = true)
+ {
+ $this->merge()->allowUnset($allow);
+
+ return $this;
+ }
+
+ /**
+ * Adds an "enabled" boolean to enable the current section.
+ *
+ * By default, the section is disabled. If any configuration is specified then
+ * the node will be automatically enabled:
+ *
+ * enableableArrayNode: {enabled: true, ...} # The config is enabled & default values get overridden
+ * enableableArrayNode: ~ # The config is enabled & use the default values
+ * enableableArrayNode: true # The config is enabled & use the default values
+ * enableableArrayNode: {other: value, ...} # The config is enabled & default values get overridden
+ * enableableArrayNode: {enabled: false, ...} # The config is disabled
+ * enableableArrayNode: false # The config is disabled
+ *
+ * @return $this
+ */
+ public function canBeEnabled()
+ {
+ $this
+ ->addDefaultsIfNotSet()
+ ->treatFalseLike(array('enabled' => false))
+ ->treatTrueLike(array('enabled' => true))
+ ->treatNullLike(array('enabled' => true))
+ ->beforeNormalization()
+ ->ifArray()
+ ->then(function ($v) {
+ $v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true;
+
+ return $v;
+ })
+ ->end()
+ ->children()
+ ->booleanNode('enabled')
+ ->defaultFalse()
+ ;
+
+ return $this;
+ }
+
+ /**
+ * Adds an "enabled" boolean to enable the current section.
+ *
+ * By default, the section is enabled.
+ *
+ * @return $this
+ */
+ public function canBeDisabled()
+ {
+ $this
+ ->addDefaultsIfNotSet()
+ ->treatFalseLike(array('enabled' => false))
+ ->treatTrueLike(array('enabled' => true))
+ ->treatNullLike(array('enabled' => true))
+ ->children()
+ ->booleanNode('enabled')
+ ->defaultTrue()
+ ;
+
+ return $this;
+ }
+
+ /**
+ * Disables the deep merging of the node.
+ *
+ * @return $this
+ */
+ public function performNoDeepMerging()
+ {
+ $this->performDeepMerging = false;
+
+ return $this;
+ }
+
+ /**
+ * Allows extra config keys to be specified under an array without
+ * throwing an exception.
+ *
+ * Those config values are simply ignored and removed from the
+ * resulting array. This should be used only in special cases where
+ * you want to send an entire configuration array through a special
+ * tree that processes only part of the array.
+ *
+ * @param bool $remove Whether to remove the extra keys
+ *
+ * @return $this
+ */
+ public function ignoreExtraKeys($remove = true)
+ {
+ $this->ignoreExtraKeys = true;
+ $this->removeExtraKeys = $remove;
+
+ return $this;
+ }
+
+ /**
+ * Sets key normalization.
+ *
+ * @param bool $bool Whether to enable key normalization
+ *
+ * @return $this
+ */
+ public function normalizeKeys($bool)
+ {
+ $this->normalizeKeys = (bool) $bool;
+
+ return $this;
+ }
+
+ /**
+ * Appends a node definition.
+ *
+ * $node = new ArrayNodeDefinition()
+ * ->children()
+ * ->scalarNode('foo')->end()
+ * ->scalarNode('baz')->end()
+ * ->end()
+ * ->append($this->getBarNodeDefinition())
+ * ;
+ *
+ * @param NodeDefinition $node A NodeDefinition instance
+ *
+ * @return $this
+ */
+ public function append(NodeDefinition $node)
+ {
+ $this->children[$node->name] = $node->setParent($this);
+
+ return $this;
+ }
+
+ /**
+ * Returns a node builder to be used to add children and prototype.
+ *
+ * @return NodeBuilder The node builder
+ */
+ protected function getNodeBuilder()
+ {
+ if (null === $this->nodeBuilder) {
+ $this->nodeBuilder = new NodeBuilder();
+ }
+
+ return $this->nodeBuilder->setParent($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function createNode()
+ {
+ if (null === $this->prototype) {
+ $node = new ArrayNode($this->name, $this->parent);
+
+ $this->validateConcreteNode($node);
+
+ $node->setAddIfNotSet($this->addDefaults);
+
+ foreach ($this->children as $child) {
+ $child->parent = $node;
+ $node->addChild($child->getNode());
+ }
+ } else {
+ $node = new PrototypedArrayNode($this->name, $this->parent);
+
+ $this->validatePrototypeNode($node);
+
+ if (null !== $this->key) {
+ $node->setKeyAttribute($this->key, $this->removeKeyItem);
+ }
+
+ if (true === $this->atLeastOne) {
+ $node->setMinNumberOfElements(1);
+ }
+
+ if ($this->default) {
+ $node->setDefaultValue($this->defaultValue);
+ }
+
+ if (false !== $this->addDefaultChildren) {
+ $node->setAddChildrenIfNoneSet($this->addDefaultChildren);
+ if ($this->prototype instanceof static && null === $this->prototype->prototype) {
+ $this->prototype->addDefaultsIfNotSet();
+ }
+ }
+
+ $this->prototype->parent = $node;
+ $node->setPrototype($this->prototype->getNode());
+ }
+
+ $node->setAllowNewKeys($this->allowNewKeys);
+ $node->addEquivalentValue(null, $this->nullEquivalent);
+ $node->addEquivalentValue(true, $this->trueEquivalent);
+ $node->addEquivalentValue(false, $this->falseEquivalent);
+ $node->setPerformDeepMerging($this->performDeepMerging);
+ $node->setRequired($this->required);
+ $node->setIgnoreExtraKeys($this->ignoreExtraKeys, $this->removeExtraKeys);
+ $node->setNormalizeKeys($this->normalizeKeys);
+
+ if (null !== $this->normalization) {
+ $node->setNormalizationClosures($this->normalization->before);
+ $node->setXmlRemappings($this->normalization->remappings);
+ }
+
+ if (null !== $this->merge) {
+ $node->setAllowOverwrite($this->merge->allowOverwrite);
+ $node->setAllowFalse($this->merge->allowFalse);
+ }
+
+ if (null !== $this->validation) {
+ $node->setFinalValidationClosures($this->validation->rules);
+ }
+
+ return $node;
+ }
+
+ /**
+ * Validate the configuration of a concrete node.
+ *
+ * @param ArrayNode $node The related node
+ *
+ * @throws InvalidDefinitionException
+ */
+ protected function validateConcreteNode(ArrayNode $node)
+ {
+ $path = $node->getPath();
+
+ if (null !== $this->key) {
+ throw new InvalidDefinitionException(
+ sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s"', $path)
+ );
+ }
+
+ if (true === $this->atLeastOne) {
+ throw new InvalidDefinitionException(
+ sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s"', $path)
+ );
+ }
+
+ if ($this->default) {
+ throw new InvalidDefinitionException(
+ sprintf('->defaultValue() is not applicable to concrete nodes at path "%s"', $path)
+ );
+ }
+
+ if (false !== $this->addDefaultChildren) {
+ throw new InvalidDefinitionException(
+ sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s"', $path)
+ );
+ }
+ }
+
+ /**
+ * Validate the configuration of a prototype node.
+ *
+ * @param PrototypedArrayNode $node The related node
+ *
+ * @throws InvalidDefinitionException
+ */
+ protected function validatePrototypeNode(PrototypedArrayNode $node)
+ {
+ $path = $node->getPath();
+
+ if ($this->addDefaults) {
+ throw new InvalidDefinitionException(
+ sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s"', $path)
+ );
+ }
+
+ if (false !== $this->addDefaultChildren) {
+ if ($this->default) {
+ throw new InvalidDefinitionException(
+ sprintf('A default value and default children might not be used together at path "%s"', $path)
+ );
+ }
+
+ if (null !== $this->key && (null === $this->addDefaultChildren || is_int($this->addDefaultChildren) && $this->addDefaultChildren > 0)) {
+ throw new InvalidDefinitionException(
+ sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s"', $path)
+ );
+ }
+
+ if (null === $this->key && (is_string($this->addDefaultChildren) || is_array($this->addDefaultChildren))) {
+ throw new InvalidDefinitionException(
+ sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s"', $path)
+ );
+ }
+ }
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Builder/BooleanNodeDefinition.php b/console/skel/symfony/config/Definition/Builder/BooleanNodeDefinition.php
new file mode 100644
index 0000000..28e5657
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/BooleanNodeDefinition.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\BooleanNode;
+use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
+
+/**
+ * This class provides a fluent interface for defining a node.
+ *
+ * @author Johannes M. Schmitt
+ */
+class BooleanNodeDefinition extends ScalarNodeDefinition
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct($name, NodeParentInterface $parent = null)
+ {
+ parent::__construct($name, $parent);
+
+ $this->nullEquivalent = true;
+ }
+
+ /**
+ * Instantiate a Node.
+ *
+ * @return BooleanNode The node
+ */
+ protected function instantiateNode()
+ {
+ return new BooleanNode($this->name, $this->parent);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws InvalidDefinitionException
+ */
+ public function cannotBeEmpty()
+ {
+ throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to BooleanNodeDefinition.');
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Builder/EnumNodeDefinition.php b/console/skel/symfony/config/Definition/Builder/EnumNodeDefinition.php
new file mode 100644
index 0000000..56047ad
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/EnumNodeDefinition.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\EnumNode;
+
+/**
+ * Enum Node Definition.
+ *
+ * @author Johannes M. Schmitt
+ */
+class EnumNodeDefinition extends ScalarNodeDefinition
+{
+ private $values;
+
+ /**
+ * @param array $values
+ *
+ * @return $this
+ */
+ public function values(array $values)
+ {
+ $values = array_unique($values);
+
+ if (empty($values)) {
+ throw new \InvalidArgumentException('->values() must be called with at least one value.');
+ }
+
+ $this->values = $values;
+
+ return $this;
+ }
+
+ /**
+ * Instantiate a Node.
+ *
+ * @return EnumNode The node
+ *
+ * @throws \RuntimeException
+ */
+ protected function instantiateNode()
+ {
+ if (null === $this->values) {
+ throw new \RuntimeException('You must call ->values() on enum nodes.');
+ }
+
+ return new EnumNode($this->name, $this->parent, $this->values);
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Builder/ExprBuilder.php b/console/skel/symfony/config/Definition/Builder/ExprBuilder.php
new file mode 100644
index 0000000..bc83b76
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/ExprBuilder.php
@@ -0,0 +1,263 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
+
+/**
+ * This class builds an if expression.
+ *
+ * @author Johannes M. Schmitt
+ * @author Christophe Coevoet
+ */
+class ExprBuilder
+{
+ protected $node;
+ public $ifPart;
+ public $thenPart;
+
+ /**
+ * Constructor.
+ *
+ * @param NodeDefinition $node The related node
+ */
+ public function __construct(NodeDefinition $node)
+ {
+ $this->node = $node;
+ }
+
+ /**
+ * Marks the expression as being always used.
+ *
+ * @param \Closure $then
+ *
+ * @return $this
+ */
+ public function always(\Closure $then = null)
+ {
+ $this->ifPart = function ($v) { return true; };
+
+ if (null !== $then) {
+ $this->thenPart = $then;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets a closure to use as tests.
+ *
+ * The default one tests if the value is true.
+ *
+ * @param \Closure $closure
+ *
+ * @return $this
+ */
+ public function ifTrue(\Closure $closure = null)
+ {
+ if (null === $closure) {
+ $closure = function ($v) { return true === $v; };
+ }
+
+ $this->ifPart = $closure;
+
+ return $this;
+ }
+
+ /**
+ * Tests if the value is a string.
+ *
+ * @return $this
+ */
+ public function ifString()
+ {
+ $this->ifPart = function ($v) { return is_string($v); };
+
+ return $this;
+ }
+
+ /**
+ * Tests if the value is null.
+ *
+ * @return $this
+ */
+ public function ifNull()
+ {
+ $this->ifPart = function ($v) { return null === $v; };
+
+ return $this;
+ }
+
+ /**
+ * Tests if the value is empty.
+ *
+ * @return ExprBuilder
+ */
+ public function ifEmpty()
+ {
+ $this->ifPart = function ($v) { return empty($v); };
+
+ return $this;
+ }
+
+ /**
+ * Tests if the value is an array.
+ *
+ * @return $this
+ */
+ public function ifArray()
+ {
+ $this->ifPart = function ($v) { return is_array($v); };
+
+ return $this;
+ }
+
+ /**
+ * Tests if the value is in an array.
+ *
+ * @param array $array
+ *
+ * @return $this
+ */
+ public function ifInArray(array $array)
+ {
+ $this->ifPart = function ($v) use ($array) { return in_array($v, $array, true); };
+
+ return $this;
+ }
+
+ /**
+ * Tests if the value is not in an array.
+ *
+ * @param array $array
+ *
+ * @return $this
+ */
+ public function ifNotInArray(array $array)
+ {
+ $this->ifPart = function ($v) use ($array) { return !in_array($v, $array, true); };
+
+ return $this;
+ }
+
+ /**
+ * Transforms variables of any type into an array.
+ *
+ * @return $this
+ */
+ public function castToArray()
+ {
+ $this->ifPart = function ($v) { return !is_array($v); };
+ $this->thenPart = function ($v) { return array($v); };
+
+ return $this;
+ }
+
+ /**
+ * Sets the closure to run if the test pass.
+ *
+ * @param \Closure $closure
+ *
+ * @return $this
+ */
+ public function then(\Closure $closure)
+ {
+ $this->thenPart = $closure;
+
+ return $this;
+ }
+
+ /**
+ * Sets a closure returning an empty array.
+ *
+ * @return $this
+ */
+ public function thenEmptyArray()
+ {
+ $this->thenPart = function ($v) { return array(); };
+
+ return $this;
+ }
+
+ /**
+ * Sets a closure marking the value as invalid at validation time.
+ *
+ * if you want to add the value of the node in your message just use a %s placeholder.
+ *
+ * @param string $message
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function thenInvalid($message)
+ {
+ $this->thenPart = function ($v) use ($message) {throw new \InvalidArgumentException(sprintf($message, json_encode($v))); };
+
+ return $this;
+ }
+
+ /**
+ * Sets a closure unsetting this key of the array at validation time.
+ *
+ * @return $this
+ *
+ * @throws UnsetKeyException
+ */
+ public function thenUnset()
+ {
+ $this->thenPart = function ($v) { throw new UnsetKeyException('Unsetting key'); };
+
+ return $this;
+ }
+
+ /**
+ * Returns the related node.
+ *
+ * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition
+ *
+ * @throws \RuntimeException
+ */
+ public function end()
+ {
+ if (null === $this->ifPart) {
+ throw new \RuntimeException('You must specify an if part.');
+ }
+ if (null === $this->thenPart) {
+ throw new \RuntimeException('You must specify a then part.');
+ }
+
+ return $this->node;
+ }
+
+ /**
+ * Builds the expressions.
+ *
+ * @param ExprBuilder[] $expressions An array of ExprBuilder instances to build
+ *
+ * @return array
+ */
+ public static function buildExpressions(array $expressions)
+ {
+ foreach ($expressions as $k => $expr) {
+ if ($expr instanceof self) {
+ $if = $expr->ifPart;
+ $then = $expr->thenPart;
+ $expressions[$k] = function ($v) use ($if, $then) {
+ return $if($v) ? $then($v) : $v;
+ };
+ }
+ }
+
+ return $expressions;
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Builder/FloatNodeDefinition.php b/console/skel/symfony/config/Definition/Builder/FloatNodeDefinition.php
new file mode 100644
index 0000000..c0bed46
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/FloatNodeDefinition.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\FloatNode;
+
+/**
+ * This class provides a fluent interface for defining a float node.
+ *
+ * @author Jeanmonod David
+ */
+class FloatNodeDefinition extends NumericNodeDefinition
+{
+ /**
+ * Instantiates a Node.
+ *
+ * @return FloatNode The node
+ */
+ protected function instantiateNode()
+ {
+ return new FloatNode($this->name, $this->parent, $this->min, $this->max);
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Builder/IntegerNodeDefinition.php b/console/skel/symfony/config/Definition/Builder/IntegerNodeDefinition.php
new file mode 100644
index 0000000..f6c3c14
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/IntegerNodeDefinition.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\IntegerNode;
+
+/**
+ * This class provides a fluent interface for defining an integer node.
+ *
+ * @author Jeanmonod David
+ */
+class IntegerNodeDefinition extends NumericNodeDefinition
+{
+ /**
+ * Instantiates a Node.
+ *
+ * @return IntegerNode The node
+ */
+ protected function instantiateNode()
+ {
+ return new IntegerNode($this->name, $this->parent, $this->min, $this->max);
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Builder/MergeBuilder.php b/console/skel/symfony/config/Definition/Builder/MergeBuilder.php
new file mode 100644
index 0000000..1d24953
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/MergeBuilder.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+/**
+ * This class builds merge conditions.
+ *
+ * @author Johannes M. Schmitt
+ */
+class MergeBuilder
+{
+ protected $node;
+ public $allowFalse = false;
+ public $allowOverwrite = true;
+
+ /**
+ * Constructor.
+ *
+ * @param NodeDefinition $node The related node
+ */
+ public function __construct(NodeDefinition $node)
+ {
+ $this->node = $node;
+ }
+
+ /**
+ * Sets whether the node can be unset.
+ *
+ * @param bool $allow
+ *
+ * @return $this
+ */
+ public function allowUnset($allow = true)
+ {
+ $this->allowFalse = $allow;
+
+ return $this;
+ }
+
+ /**
+ * Sets whether the node can be overwritten.
+ *
+ * @param bool $deny Whether the overwriting is forbidden or not
+ *
+ * @return $this
+ */
+ public function denyOverwrite($deny = true)
+ {
+ $this->allowOverwrite = !$deny;
+
+ return $this;
+ }
+
+ /**
+ * Returns the related node.
+ *
+ * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition
+ */
+ public function end()
+ {
+ return $this->node;
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Builder/NodeBuilder.php b/console/skel/symfony/config/Definition/Builder/NodeBuilder.php
new file mode 100644
index 0000000..e780777
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/NodeBuilder.php
@@ -0,0 +1,245 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+/**
+ * This class provides a fluent interface for building a node.
+ *
+ * @author Johannes M. Schmitt
+ */
+class NodeBuilder implements NodeParentInterface
+{
+ protected $parent;
+ protected $nodeMapping;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->nodeMapping = array(
+ 'variable' => __NAMESPACE__.'\\VariableNodeDefinition',
+ 'scalar' => __NAMESPACE__.'\\ScalarNodeDefinition',
+ 'boolean' => __NAMESPACE__.'\\BooleanNodeDefinition',
+ 'integer' => __NAMESPACE__.'\\IntegerNodeDefinition',
+ 'float' => __NAMESPACE__.'\\FloatNodeDefinition',
+ 'array' => __NAMESPACE__.'\\ArrayNodeDefinition',
+ 'enum' => __NAMESPACE__.'\\EnumNodeDefinition',
+ );
+ }
+
+ /**
+ * Set the parent node.
+ *
+ * @param ParentNodeDefinitionInterface $parent The parent node
+ *
+ * @return $this
+ */
+ public function setParent(ParentNodeDefinitionInterface $parent = null)
+ {
+ $this->parent = $parent;
+
+ return $this;
+ }
+
+ /**
+ * Creates a child array node.
+ *
+ * @param string $name The name of the node
+ *
+ * @return ArrayNodeDefinition The child node
+ */
+ public function arrayNode($name)
+ {
+ return $this->node($name, 'array');
+ }
+
+ /**
+ * Creates a child scalar node.
+ *
+ * @param string $name the name of the node
+ *
+ * @return ScalarNodeDefinition The child node
+ */
+ public function scalarNode($name)
+ {
+ return $this->node($name, 'scalar');
+ }
+
+ /**
+ * Creates a child Boolean node.
+ *
+ * @param string $name The name of the node
+ *
+ * @return BooleanNodeDefinition The child node
+ */
+ public function booleanNode($name)
+ {
+ return $this->node($name, 'boolean');
+ }
+
+ /**
+ * Creates a child integer node.
+ *
+ * @param string $name the name of the node
+ *
+ * @return IntegerNodeDefinition The child node
+ */
+ public function integerNode($name)
+ {
+ return $this->node($name, 'integer');
+ }
+
+ /**
+ * Creates a child float node.
+ *
+ * @param string $name the name of the node
+ *
+ * @return FloatNodeDefinition The child node
+ */
+ public function floatNode($name)
+ {
+ return $this->node($name, 'float');
+ }
+
+ /**
+ * Creates a child EnumNode.
+ *
+ * @param string $name
+ *
+ * @return EnumNodeDefinition
+ */
+ public function enumNode($name)
+ {
+ return $this->node($name, 'enum');
+ }
+
+ /**
+ * Creates a child variable node.
+ *
+ * @param string $name The name of the node
+ *
+ * @return VariableNodeDefinition The builder of the child node
+ */
+ public function variableNode($name)
+ {
+ return $this->node($name, 'variable');
+ }
+
+ /**
+ * Returns the parent node.
+ *
+ * @return ParentNodeDefinitionInterface|NodeDefinition The parent node
+ */
+ public function end()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Creates a child node.
+ *
+ * @param string $name The name of the node
+ * @param string $type The type of the node
+ *
+ * @return NodeDefinition The child node
+ *
+ * @throws \RuntimeException When the node type is not registered
+ * @throws \RuntimeException When the node class is not found
+ */
+ public function node($name, $type)
+ {
+ $class = $this->getNodeClass($type);
+
+ $node = new $class($name);
+
+ $this->append($node);
+
+ return $node;
+ }
+
+ /**
+ * Appends a node definition.
+ *
+ * Usage:
+ *
+ * $node = new ArrayNodeDefinition('name')
+ * ->children()
+ * ->scalarNode('foo')->end()
+ * ->scalarNode('baz')->end()
+ * ->append($this->getBarNodeDefinition())
+ * ->end()
+ * ;
+ *
+ * @param NodeDefinition $node
+ *
+ * @return $this
+ */
+ public function append(NodeDefinition $node)
+ {
+ if ($node instanceof ParentNodeDefinitionInterface) {
+ $builder = clone $this;
+ $builder->setParent(null);
+ $node->setBuilder($builder);
+ }
+
+ if (null !== $this->parent) {
+ $this->parent->append($node);
+ // Make this builder the node parent to allow for a fluid interface
+ $node->setParent($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds or overrides a node Type.
+ *
+ * @param string $type The name of the type
+ * @param string $class The fully qualified name the node definition class
+ *
+ * @return $this
+ */
+ public function setNodeClass($type, $class)
+ {
+ $this->nodeMapping[strtolower($type)] = $class;
+
+ return $this;
+ }
+
+ /**
+ * Returns the class name of the node definition.
+ *
+ * @param string $type The node type
+ *
+ * @return string The node definition class name
+ *
+ * @throws \RuntimeException When the node type is not registered
+ * @throws \RuntimeException When the node class is not found
+ */
+ protected function getNodeClass($type)
+ {
+ $type = strtolower($type);
+
+ if (!isset($this->nodeMapping[$type])) {
+ throw new \RuntimeException(sprintf('The node type "%s" is not registered.', $type));
+ }
+
+ $class = $this->nodeMapping[$type];
+
+ if (!class_exists($class)) {
+ throw new \RuntimeException(sprintf('The node class "%s" does not exist.', $class));
+ }
+
+ return $class;
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Builder/NodeDefinition.php b/console/skel/symfony/config/Definition/Builder/NodeDefinition.php
new file mode 100644
index 0000000..1b712a3
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/NodeDefinition.php
@@ -0,0 +1,343 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\NodeInterface;
+use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
+
+/**
+ * This class provides a fluent interface for defining a node.
+ *
+ * @author Johannes M. Schmitt
+ */
+abstract class NodeDefinition implements NodeParentInterface
+{
+ protected $name;
+ protected $normalization;
+ protected $validation;
+ protected $defaultValue;
+ protected $default = false;
+ protected $required = false;
+ protected $merge;
+ protected $allowEmptyValue = true;
+ protected $nullEquivalent;
+ protected $trueEquivalent = true;
+ protected $falseEquivalent = false;
+
+ /**
+ * @var NodeParentInterface|null
+ */
+ protected $parent;
+ protected $attributes = array();
+
+ /**
+ * Constructor.
+ *
+ * @param string $name The name of the node
+ * @param NodeParentInterface|null $parent The parent
+ */
+ public function __construct($name, NodeParentInterface $parent = null)
+ {
+ $this->parent = $parent;
+ $this->name = $name;
+ }
+
+ /**
+ * Sets the parent node.
+ *
+ * @param NodeParentInterface $parent The parent
+ *
+ * @return $this
+ */
+ public function setParent(NodeParentInterface $parent)
+ {
+ $this->parent = $parent;
+
+ return $this;
+ }
+
+ /**
+ * Sets info message.
+ *
+ * @param string $info The info text
+ *
+ * @return $this
+ */
+ public function info($info)
+ {
+ return $this->attribute('info', $info);
+ }
+
+ /**
+ * Sets example configuration.
+ *
+ * @param string|array $example
+ *
+ * @return $this
+ */
+ public function example($example)
+ {
+ return $this->attribute('example', $example);
+ }
+
+ /**
+ * Sets an attribute on the node.
+ *
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function attribute($key, $value)
+ {
+ $this->attributes[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Returns the parent node.
+ *
+ * @return NodeParentInterface|NodeBuilder|NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition|null The builder of the parent node
+ */
+ public function end()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Creates the node.
+ *
+ * @param bool $forceRootNode Whether to force this node as the root node
+ *
+ * @return NodeInterface
+ */
+ public function getNode($forceRootNode = false)
+ {
+ if ($forceRootNode) {
+ $this->parent = null;
+ }
+
+ if (null !== $this->normalization) {
+ $this->normalization->before = ExprBuilder::buildExpressions($this->normalization->before);
+ }
+
+ if (null !== $this->validation) {
+ $this->validation->rules = ExprBuilder::buildExpressions($this->validation->rules);
+ }
+
+ $node = $this->createNode();
+ $node->setAttributes($this->attributes);
+
+ return $node;
+ }
+
+ /**
+ * Sets the default value.
+ *
+ * @param mixed $value The default value
+ *
+ * @return $this
+ */
+ public function defaultValue($value)
+ {
+ $this->default = true;
+ $this->defaultValue = $value;
+
+ return $this;
+ }
+
+ /**
+ * Sets the node as required.
+ *
+ * @return $this
+ */
+ public function isRequired()
+ {
+ $this->required = true;
+
+ return $this;
+ }
+
+ /**
+ * Sets the equivalent value used when the node contains null.
+ *
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function treatNullLike($value)
+ {
+ $this->nullEquivalent = $value;
+
+ return $this;
+ }
+
+ /**
+ * Sets the equivalent value used when the node contains true.
+ *
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function treatTrueLike($value)
+ {
+ $this->trueEquivalent = $value;
+
+ return $this;
+ }
+
+ /**
+ * Sets the equivalent value used when the node contains false.
+ *
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function treatFalseLike($value)
+ {
+ $this->falseEquivalent = $value;
+
+ return $this;
+ }
+
+ /**
+ * Sets null as the default value.
+ *
+ * @return $this
+ */
+ public function defaultNull()
+ {
+ return $this->defaultValue(null);
+ }
+
+ /**
+ * Sets true as the default value.
+ *
+ * @return $this
+ */
+ public function defaultTrue()
+ {
+ return $this->defaultValue(true);
+ }
+
+ /**
+ * Sets false as the default value.
+ *
+ * @return $this
+ */
+ public function defaultFalse()
+ {
+ return $this->defaultValue(false);
+ }
+
+ /**
+ * Sets an expression to run before the normalization.
+ *
+ * @return ExprBuilder
+ */
+ public function beforeNormalization()
+ {
+ return $this->normalization()->before();
+ }
+
+ /**
+ * Denies the node value being empty.
+ *
+ * @return $this
+ */
+ public function cannotBeEmpty()
+ {
+ $this->allowEmptyValue = false;
+
+ return $this;
+ }
+
+ /**
+ * Sets an expression to run for the validation.
+ *
+ * The expression receives the value of the node and must return it. It can
+ * modify it.
+ * An exception should be thrown when the node is not valid.
+ *
+ * @return ExprBuilder
+ */
+ public function validate()
+ {
+ return $this->validation()->rule();
+ }
+
+ /**
+ * Sets whether the node can be overwritten.
+ *
+ * @param bool $deny Whether the overwriting is forbidden or not
+ *
+ * @return $this
+ */
+ public function cannotBeOverwritten($deny = true)
+ {
+ $this->merge()->denyOverwrite($deny);
+
+ return $this;
+ }
+
+ /**
+ * Gets the builder for validation rules.
+ *
+ * @return ValidationBuilder
+ */
+ protected function validation()
+ {
+ if (null === $this->validation) {
+ $this->validation = new ValidationBuilder($this);
+ }
+
+ return $this->validation;
+ }
+
+ /**
+ * Gets the builder for merging rules.
+ *
+ * @return MergeBuilder
+ */
+ protected function merge()
+ {
+ if (null === $this->merge) {
+ $this->merge = new MergeBuilder($this);
+ }
+
+ return $this->merge;
+ }
+
+ /**
+ * Gets the builder for normalization rules.
+ *
+ * @return NormalizationBuilder
+ */
+ protected function normalization()
+ {
+ if (null === $this->normalization) {
+ $this->normalization = new NormalizationBuilder($this);
+ }
+
+ return $this->normalization;
+ }
+
+ /**
+ * Instantiate and configure the node according to this definition.
+ *
+ * @return NodeInterface $node The node instance
+ *
+ * @throws InvalidDefinitionException When the definition is invalid
+ */
+ abstract protected function createNode();
+}
diff --git a/console/skel/symfony/config/Definition/Builder/NodeParentInterface.php b/console/skel/symfony/config/Definition/Builder/NodeParentInterface.php
new file mode 100644
index 0000000..305e993
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/NodeParentInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+/**
+ * An interface that must be implemented by all node parents.
+ *
+ * @author Victor Berchet
+ */
+interface NodeParentInterface
+{
+}
diff --git a/console/skel/symfony/config/Definition/Builder/NormalizationBuilder.php b/console/skel/symfony/config/Definition/Builder/NormalizationBuilder.php
new file mode 100644
index 0000000..ae642a2
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/NormalizationBuilder.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+/**
+ * This class builds normalization conditions.
+ *
+ * @author Johannes M. Schmitt
+ */
+class NormalizationBuilder
+{
+ protected $node;
+ public $before = array();
+ public $remappings = array();
+
+ /**
+ * Constructor.
+ *
+ * @param NodeDefinition $node The related node
+ */
+ public function __construct(NodeDefinition $node)
+ {
+ $this->node = $node;
+ }
+
+ /**
+ * Registers a key to remap to its plural form.
+ *
+ * @param string $key The key to remap
+ * @param string $plural The plural of the key in case of irregular plural
+ *
+ * @return $this
+ */
+ public function remap($key, $plural = null)
+ {
+ $this->remappings[] = array($key, null === $plural ? $key.'s' : $plural);
+
+ return $this;
+ }
+
+ /**
+ * Registers a closure to run before the normalization or an expression builder to build it if null is provided.
+ *
+ * @param \Closure $closure
+ *
+ * @return ExprBuilder|$this
+ */
+ public function before(\Closure $closure = null)
+ {
+ if (null !== $closure) {
+ $this->before[] = $closure;
+
+ return $this;
+ }
+
+ return $this->before[] = new ExprBuilder($this->node);
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Builder/NumericNodeDefinition.php b/console/skel/symfony/config/Definition/Builder/NumericNodeDefinition.php
new file mode 100644
index 0000000..0d0207c
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/NumericNodeDefinition.php
@@ -0,0 +1,73 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
+
+/**
+ * Abstract class that contains common code of integer and float node definitions.
+ *
+ * @author David Jeanmonod
+ */
+abstract class NumericNodeDefinition extends ScalarNodeDefinition
+{
+ protected $min;
+ protected $max;
+
+ /**
+ * Ensures that the value is smaller than the given reference.
+ *
+ * @param mixed $max
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException when the constraint is inconsistent
+ */
+ public function max($max)
+ {
+ if (isset($this->min) && $this->min > $max) {
+ throw new \InvalidArgumentException(sprintf('You cannot define a max(%s) as you already have a min(%s)', $max, $this->min));
+ }
+ $this->max = $max;
+
+ return $this;
+ }
+
+ /**
+ * Ensures that the value is bigger than the given reference.
+ *
+ * @param mixed $min
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException when the constraint is inconsistent
+ */
+ public function min($min)
+ {
+ if (isset($this->max) && $this->max < $min) {
+ throw new \InvalidArgumentException(sprintf('You cannot define a min(%s) as you already have a max(%s)', $min, $this->max));
+ }
+ $this->min = $min;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws InvalidDefinitionException
+ */
+ public function cannotBeEmpty()
+ {
+ throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to NumericNodeDefinition.');
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php b/console/skel/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php
new file mode 100644
index 0000000..575495b
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+/**
+ * An interface that must be implemented by nodes which can have children.
+ *
+ * @author Victor Berchet
+ */
+interface ParentNodeDefinitionInterface
+{
+ public function children();
+
+ public function append(NodeDefinition $node);
+
+ public function setBuilder(NodeBuilder $builder);
+}
diff --git a/console/skel/symfony/config/Definition/Builder/ScalarNodeDefinition.php b/console/skel/symfony/config/Definition/Builder/ScalarNodeDefinition.php
new file mode 100644
index 0000000..6170555
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/ScalarNodeDefinition.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\ScalarNode;
+
+/**
+ * This class provides a fluent interface for defining a node.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ScalarNodeDefinition extends VariableNodeDefinition
+{
+ /**
+ * Instantiate a Node.
+ *
+ * @return ScalarNode The node
+ */
+ protected function instantiateNode()
+ {
+ return new ScalarNode($this->name, $this->parent);
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Builder/TreeBuilder.php b/console/skel/symfony/config/Definition/Builder/TreeBuilder.php
new file mode 100644
index 0000000..5d02848
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/TreeBuilder.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\NodeInterface;
+
+/**
+ * This is the entry class for building a config tree.
+ *
+ * @author Johannes M. Schmitt
+ */
+class TreeBuilder implements NodeParentInterface
+{
+ protected $tree;
+ protected $root;
+ protected $builder;
+
+ /**
+ * Creates the root node.
+ *
+ * @param string $name The name of the root node
+ * @param string $type The type of the root node
+ * @param NodeBuilder $builder A custom node builder instance
+ *
+ * @return ArrayNodeDefinition|NodeDefinition The root node (as an ArrayNodeDefinition when the type is 'array')
+ *
+ * @throws \RuntimeException When the node type is not supported
+ */
+ public function root($name, $type = 'array', NodeBuilder $builder = null)
+ {
+ $builder = $builder ?: new NodeBuilder();
+
+ return $this->root = $builder->node($name, $type)->setParent($this);
+ }
+
+ /**
+ * Builds the tree.
+ *
+ * @return NodeInterface
+ *
+ * @throws \RuntimeException
+ */
+ public function buildTree()
+ {
+ if (null === $this->root) {
+ throw new \RuntimeException('The configuration tree has no root node.');
+ }
+ if (null !== $this->tree) {
+ return $this->tree;
+ }
+
+ return $this->tree = $this->root->getNode(true);
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Builder/ValidationBuilder.php b/console/skel/symfony/config/Definition/Builder/ValidationBuilder.php
new file mode 100644
index 0000000..12aa59a
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/ValidationBuilder.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+/**
+ * This class builds validation conditions.
+ *
+ * @author Christophe Coevoet
+ */
+class ValidationBuilder
+{
+ protected $node;
+ public $rules = array();
+
+ /**
+ * Constructor.
+ *
+ * @param NodeDefinition $node The related node
+ */
+ public function __construct(NodeDefinition $node)
+ {
+ $this->node = $node;
+ }
+
+ /**
+ * Registers a closure to run as normalization or an expression builder to build it if null is provided.
+ *
+ * @param \Closure $closure
+ *
+ * @return ExprBuilder|$this
+ */
+ public function rule(\Closure $closure = null)
+ {
+ if (null !== $closure) {
+ $this->rules[] = $closure;
+
+ return $this;
+ }
+
+ return $this->rules[] = new ExprBuilder($this->node);
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Builder/VariableNodeDefinition.php b/console/skel/symfony/config/Definition/Builder/VariableNodeDefinition.php
new file mode 100644
index 0000000..a46b7ea
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Builder/VariableNodeDefinition.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\VariableNode;
+
+/**
+ * This class provides a fluent interface for defining a node.
+ *
+ * @author Johannes M. Schmitt
+ */
+class VariableNodeDefinition extends NodeDefinition
+{
+ /**
+ * Instantiate a Node.
+ *
+ * @return VariableNode The node
+ */
+ protected function instantiateNode()
+ {
+ return new VariableNode($this->name, $this->parent);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function createNode()
+ {
+ $node = $this->instantiateNode();
+
+ if (null !== $this->normalization) {
+ $node->setNormalizationClosures($this->normalization->before);
+ }
+
+ if (null !== $this->merge) {
+ $node->setAllowOverwrite($this->merge->allowOverwrite);
+ }
+
+ if (true === $this->default) {
+ $node->setDefaultValue($this->defaultValue);
+ }
+
+ $node->setAllowEmptyValue($this->allowEmptyValue);
+ $node->addEquivalentValue(null, $this->nullEquivalent);
+ $node->addEquivalentValue(true, $this->trueEquivalent);
+ $node->addEquivalentValue(false, $this->falseEquivalent);
+ $node->setRequired($this->required);
+
+ if (null !== $this->validation) {
+ $node->setFinalValidationClosures($this->validation->rules);
+ }
+
+ return $node;
+ }
+}
diff --git a/console/skel/symfony/config/Definition/ConfigurationInterface.php b/console/skel/symfony/config/Definition/ConfigurationInterface.php
new file mode 100644
index 0000000..d6456ed
--- /dev/null
+++ b/console/skel/symfony/config/Definition/ConfigurationInterface.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+/**
+ * Configuration interface.
+ *
+ * @author Victor Berchet
+ */
+interface ConfigurationInterface
+{
+ /**
+ * Generates the configuration tree builder.
+ *
+ * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder
+ */
+ public function getConfigTreeBuilder();
+}
diff --git a/console/skel/symfony/config/Definition/Dumper/XmlReferenceDumper.php b/console/skel/symfony/config/Definition/Dumper/XmlReferenceDumper.php
new file mode 100644
index 0000000..ec5460f
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Dumper/XmlReferenceDumper.php
@@ -0,0 +1,307 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Dumper;
+
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+use Symfony\Component\Config\Definition\NodeInterface;
+use Symfony\Component\Config\Definition\ArrayNode;
+use Symfony\Component\Config\Definition\EnumNode;
+use Symfony\Component\Config\Definition\PrototypedArrayNode;
+
+/**
+ * Dumps a XML reference configuration for the given configuration/node instance.
+ *
+ * @author Wouter J
+ */
+class XmlReferenceDumper
+{
+ private $reference;
+
+ public function dump(ConfigurationInterface $configuration, $namespace = null)
+ {
+ return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace);
+ }
+
+ public function dumpNode(NodeInterface $node, $namespace = null)
+ {
+ $this->reference = '';
+ $this->writeNode($node, 0, true, $namespace);
+ $ref = $this->reference;
+ $this->reference = null;
+
+ return $ref;
+ }
+
+ /**
+ * @param NodeInterface $node
+ * @param int $depth
+ * @param bool $root If the node is the root node
+ * @param string $namespace The namespace of the node
+ */
+ private function writeNode(NodeInterface $node, $depth = 0, $root = false, $namespace = null)
+ {
+ $rootName = ($root ? 'config' : $node->getName());
+ $rootNamespace = ($namespace ?: ($root ? 'http://example.org/schema/dic/'.$node->getName() : null));
+
+ // xml remapping
+ if ($node->getParent()) {
+ $remapping = array_filter($node->getParent()->getXmlRemappings(), function ($mapping) use ($rootName) {
+ return $rootName === $mapping[1];
+ });
+
+ if (count($remapping)) {
+ list($singular) = current($remapping);
+ $rootName = $singular;
+ }
+ }
+ $rootName = str_replace('_', '-', $rootName);
+
+ $rootAttributes = array();
+ $rootAttributeComments = array();
+ $rootChildren = array();
+ $rootComments = array();
+
+ if ($node instanceof ArrayNode) {
+ $children = $node->getChildren();
+
+ // comments about the root node
+ if ($rootInfo = $node->getInfo()) {
+ $rootComments[] = $rootInfo;
+ }
+
+ if ($rootNamespace) {
+ $rootComments[] = 'Namespace: '.$rootNamespace;
+ }
+
+ // render prototyped nodes
+ if ($node instanceof PrototypedArrayNode) {
+ $prototype = $node->getPrototype();
+
+ $info = 'prototype';
+ if (null !== $prototype->getInfo()) {
+ $info .= ': '.$prototype->getInfo();
+ }
+ array_unshift($rootComments, $info);
+
+ if ($key = $node->getKeyAttribute()) {
+ $rootAttributes[$key] = str_replace('-', ' ', $rootName).' '.$key;
+ }
+
+ if ($prototype instanceof PrototypedArrayNode) {
+ $prototype->setName($key);
+ $children = array($key => $prototype);
+ } elseif ($prototype instanceof ArrayNode) {
+ $children = $prototype->getChildren();
+ } else {
+ if ($prototype->hasDefaultValue()) {
+ $prototypeValue = $prototype->getDefaultValue();
+ } else {
+ switch (get_class($prototype)) {
+ case 'Symfony\Component\Config\Definition\ScalarNode':
+ $prototypeValue = 'scalar value';
+ break;
+
+ case 'Symfony\Component\Config\Definition\FloatNode':
+ case 'Symfony\Component\Config\Definition\IntegerNode':
+ $prototypeValue = 'numeric value';
+ break;
+
+ case 'Symfony\Component\Config\Definition\BooleanNode':
+ $prototypeValue = 'true|false';
+ break;
+
+ case 'Symfony\Component\Config\Definition\EnumNode':
+ $prototypeValue = implode('|', array_map('json_encode', $prototype->getValues()));
+ break;
+
+ default:
+ $prototypeValue = 'value';
+ }
+ }
+ }
+ }
+
+ // get attributes and elements
+ foreach ($children as $child) {
+ if (!$child instanceof ArrayNode) {
+ // get attributes
+
+ // metadata
+ $name = str_replace('_', '-', $child->getName());
+ $value = '%%%%not_defined%%%%'; // use a string which isn't used in the normal world
+
+ // comments
+ $comments = array();
+ if ($info = $child->getInfo()) {
+ $comments[] = $info;
+ }
+
+ if ($example = $child->getExample()) {
+ $comments[] = 'Example: '.$example;
+ }
+
+ if ($child->isRequired()) {
+ $comments[] = 'Required';
+ }
+
+ if ($child instanceof EnumNode) {
+ $comments[] = 'One of '.implode('; ', array_map('json_encode', $child->getValues()));
+ }
+
+ if (count($comments)) {
+ $rootAttributeComments[$name] = implode(";\n", $comments);
+ }
+
+ // default values
+ if ($child->hasDefaultValue()) {
+ $value = $child->getDefaultValue();
+ }
+
+ // append attribute
+ $rootAttributes[$name] = $value;
+ } else {
+ // get elements
+ $rootChildren[] = $child;
+ }
+ }
+ }
+
+ // render comments
+
+ // root node comment
+ if (count($rootComments)) {
+ foreach ($rootComments as $comment) {
+ $this->writeLine('', $depth);
+ }
+ }
+
+ // attribute comments
+ if (count($rootAttributeComments)) {
+ foreach ($rootAttributeComments as $attrName => $comment) {
+ $commentDepth = $depth + 4 + strlen($attrName) + 2;
+ $commentLines = explode("\n", $comment);
+ $multiline = (count($commentLines) > 1);
+ $comment = implode(PHP_EOL.str_repeat(' ', $commentDepth), $commentLines);
+
+ if ($multiline) {
+ $this->writeLine('', $depth);
+ } else {
+ $this->writeLine('', $depth);
+ }
+ }
+ }
+
+ // render start tag + attributes
+ $rootIsVariablePrototype = isset($prototypeValue);
+ $rootIsEmptyTag = (0 === count($rootChildren) && !$rootIsVariablePrototype);
+ $rootOpenTag = '<'.$rootName;
+ if (1 >= ($attributesCount = count($rootAttributes))) {
+ if (1 === $attributesCount) {
+ $rootOpenTag .= sprintf(' %s="%s"', current(array_keys($rootAttributes)), $this->writeValue(current($rootAttributes)));
+ }
+
+ $rootOpenTag .= $rootIsEmptyTag ? ' />' : '>';
+
+ if ($rootIsVariablePrototype) {
+ $rootOpenTag .= $prototypeValue.''.$rootName.'>';
+ }
+
+ $this->writeLine($rootOpenTag, $depth);
+ } else {
+ $this->writeLine($rootOpenTag, $depth);
+
+ $i = 1;
+
+ foreach ($rootAttributes as $attrName => $attrValue) {
+ $attr = sprintf('%s="%s"', $attrName, $this->writeValue($attrValue));
+
+ $this->writeLine($attr, $depth + 4);
+
+ if ($attributesCount === $i++) {
+ $this->writeLine($rootIsEmptyTag ? '/>' : '>', $depth);
+
+ if ($rootIsVariablePrototype) {
+ $rootOpenTag .= $prototypeValue.''.$rootName.'>';
+ }
+ }
+ }
+ }
+
+ // render children tags
+ foreach ($rootChildren as $child) {
+ $this->writeLine('');
+ $this->writeNode($child, $depth + 4);
+ }
+
+ // render end tag
+ if (!$rootIsEmptyTag && !$rootIsVariablePrototype) {
+ $this->writeLine('');
+
+ $rootEndTag = ''.$rootName.'>';
+ $this->writeLine($rootEndTag, $depth);
+ }
+ }
+
+ /**
+ * Outputs a single config reference line.
+ *
+ * @param string $text
+ * @param int $indent
+ */
+ private function writeLine($text, $indent = 0)
+ {
+ $indent = strlen($text) + $indent;
+ $format = '%'.$indent.'s';
+
+ $this->reference .= sprintf($format, $text).PHP_EOL;
+ }
+
+ /**
+ * Renders the string conversion of the value.
+ *
+ * @param mixed $value
+ *
+ * @return string
+ */
+ private function writeValue($value)
+ {
+ if ('%%%%not_defined%%%%' === $value) {
+ return '';
+ }
+
+ if (is_string($value) || is_numeric($value)) {
+ return $value;
+ }
+
+ if (false === $value) {
+ return 'false';
+ }
+
+ if (true === $value) {
+ return 'true';
+ }
+
+ if (null === $value) {
+ return 'null';
+ }
+
+ if (empty($value)) {
+ return '';
+ }
+
+ if (is_array($value)) {
+ return implode(',', $value);
+ }
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Dumper/YamlReferenceDumper.php b/console/skel/symfony/config/Definition/Dumper/YamlReferenceDumper.php
new file mode 100644
index 0000000..1607635
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Dumper/YamlReferenceDumper.php
@@ -0,0 +1,250 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Dumper;
+
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+use Symfony\Component\Config\Definition\NodeInterface;
+use Symfony\Component\Config\Definition\ArrayNode;
+use Symfony\Component\Config\Definition\EnumNode;
+use Symfony\Component\Config\Definition\PrototypedArrayNode;
+use Symfony\Component\Config\Definition\ScalarNode;
+use Symfony\Component\Yaml\Inline;
+
+/**
+ * Dumps a Yaml reference configuration for the given configuration/node instance.
+ *
+ * @author Kevin Bond
+ */
+class YamlReferenceDumper
+{
+ private $reference;
+
+ public function dump(ConfigurationInterface $configuration)
+ {
+ return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree());
+ }
+
+ public function dumpAtPath(ConfigurationInterface $configuration, $path)
+ {
+ $rootNode = $node = $configuration->getConfigTreeBuilder()->buildTree();
+
+ foreach (explode('.', $path) as $step) {
+ if (!$node instanceof ArrayNode) {
+ throw new \UnexpectedValueException(sprintf('Unable to find node at path "%s.%s"', $rootNode->getName(), $path));
+ }
+
+ /** @var NodeInterface[] $children */
+ $children = $node instanceof PrototypedArrayNode ? $this->getPrototypeChildren($node) : $node->getChildren();
+
+ foreach ($children as $child) {
+ if ($child->getName() === $step) {
+ $node = $child;
+
+ continue 2;
+ }
+ }
+
+ throw new \UnexpectedValueException(sprintf('Unable to find node at path "%s.%s"', $rootNode->getName(), $path));
+ }
+
+ return $this->dumpNode($node);
+ }
+
+ public function dumpNode(NodeInterface $node)
+ {
+ $this->reference = '';
+ $this->writeNode($node);
+ $ref = $this->reference;
+ $this->reference = null;
+
+ return $ref;
+ }
+
+ /**
+ * @param NodeInterface $node
+ * @param int $depth
+ * @param bool $prototypedArray
+ */
+ private function writeNode(NodeInterface $node, $depth = 0, $prototypedArray = false)
+ {
+ $comments = array();
+ $default = '';
+ $defaultArray = null;
+ $children = null;
+ $example = $node->getExample();
+
+ // defaults
+ if ($node instanceof ArrayNode) {
+ $children = $node->getChildren();
+
+ if ($node instanceof PrototypedArrayNode) {
+ $children = $this->getPrototypeChildren($node);
+ }
+
+ if (!$children) {
+ if ($node->hasDefaultValue() && count($defaultArray = $node->getDefaultValue())) {
+ $default = '';
+ } elseif (!is_array($example)) {
+ $default = '[]';
+ }
+ }
+ } elseif ($node instanceof EnumNode) {
+ $comments[] = 'One of '.implode('; ', array_map('json_encode', $node->getValues()));
+ $default = $node->hasDefaultValue() ? Inline::dump($node->getDefaultValue()) : '~';
+ } else {
+ $default = '~';
+
+ if ($node->hasDefaultValue()) {
+ $default = $node->getDefaultValue();
+
+ if (is_array($default)) {
+ if (count($defaultArray = $node->getDefaultValue())) {
+ $default = '';
+ } elseif (!is_array($example)) {
+ $default = '[]';
+ }
+ } else {
+ $default = Inline::dump($default);
+ }
+ }
+ }
+
+ // required?
+ if ($node->isRequired()) {
+ $comments[] = 'Required';
+ }
+
+ // example
+ if ($example && !is_array($example)) {
+ $comments[] = 'Example: '.$example;
+ }
+
+ $default = (string) $default != '' ? ' '.$default : '';
+ $comments = count($comments) ? '# '.implode(', ', $comments) : '';
+
+ $key = $prototypedArray ? '-' : $node->getName().':';
+ $text = rtrim(sprintf('%-21s%s %s', $key, $default, $comments), ' ');
+
+ if ($info = $node->getInfo()) {
+ $this->writeLine('');
+ // indenting multi-line info
+ $info = str_replace("\n", sprintf("\n%".($depth * 4).'s# ', ' '), $info);
+ $this->writeLine('# '.$info, $depth * 4);
+ }
+
+ $this->writeLine($text, $depth * 4);
+
+ // output defaults
+ if ($defaultArray) {
+ $this->writeLine('');
+
+ $message = count($defaultArray) > 1 ? 'Defaults' : 'Default';
+
+ $this->writeLine('# '.$message.':', $depth * 4 + 4);
+
+ $this->writeArray($defaultArray, $depth + 1);
+ }
+
+ if (is_array($example)) {
+ $this->writeLine('');
+
+ $message = count($example) > 1 ? 'Examples' : 'Example';
+
+ $this->writeLine('# '.$message.':', $depth * 4 + 4);
+
+ $this->writeArray($example, $depth + 1);
+ }
+
+ if ($children) {
+ foreach ($children as $childNode) {
+ $this->writeNode($childNode, $depth + 1, $node instanceof PrototypedArrayNode && !$node->getKeyAttribute());
+ }
+ }
+ }
+
+ /**
+ * Outputs a single config reference line.
+ *
+ * @param string $text
+ * @param int $indent
+ */
+ private function writeLine($text, $indent = 0)
+ {
+ $indent = strlen($text) + $indent;
+ $format = '%'.$indent.'s';
+
+ $this->reference .= sprintf($format, $text)."\n";
+ }
+
+ private function writeArray(array $array, $depth)
+ {
+ $isIndexed = array_values($array) === $array;
+
+ foreach ($array as $key => $value) {
+ if (is_array($value)) {
+ $val = '';
+ } else {
+ $val = $value;
+ }
+
+ if ($isIndexed) {
+ $this->writeLine('- '.$val, $depth * 4);
+ } else {
+ $this->writeLine(sprintf('%-20s %s', $key.':', $val), $depth * 4);
+ }
+
+ if (is_array($value)) {
+ $this->writeArray($value, $depth + 1);
+ }
+ }
+ }
+
+ /**
+ * @param PrototypedArrayNode $node
+ *
+ * @return array
+ */
+ private function getPrototypeChildren(PrototypedArrayNode $node)
+ {
+ $prototype = $node->getPrototype();
+ $key = $node->getKeyAttribute();
+
+ // Do not expand prototype if it isn't an array node nor uses attribute as key
+ if (!$key && !$prototype instanceof ArrayNode) {
+ return $node->getChildren();
+ }
+
+ if ($prototype instanceof ArrayNode) {
+ $keyNode = new ArrayNode($key, $node);
+ $children = $prototype->getChildren();
+
+ if ($prototype instanceof PrototypedArrayNode && $prototype->getKeyAttribute()) {
+ $children = $this->getPrototypeChildren($prototype);
+ }
+
+ // add children
+ foreach ($children as $childNode) {
+ $keyNode->addChild($childNode);
+ }
+ } else {
+ $keyNode = new ScalarNode($key, $node);
+ }
+
+ $info = 'Prototype';
+ if (null !== $prototype->getInfo()) {
+ $info .= ': '.$prototype->getInfo();
+ }
+ $keyNode->setInfo($info);
+
+ return array($key => $keyNode);
+ }
+}
diff --git a/console/skel/symfony/config/Definition/EnumNode.php b/console/skel/symfony/config/Definition/EnumNode.php
new file mode 100644
index 0000000..9b4c415
--- /dev/null
+++ b/console/skel/symfony/config/Definition/EnumNode.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+
+/**
+ * Node which only allows a finite set of values.
+ *
+ * @author Johannes M. Schmitt
+ */
+class EnumNode extends ScalarNode
+{
+ private $values;
+
+ public function __construct($name, NodeInterface $parent = null, array $values = array())
+ {
+ $values = array_unique($values);
+ if (empty($values)) {
+ throw new \InvalidArgumentException('$values must contain at least one element.');
+ }
+
+ parent::__construct($name, $parent);
+ $this->values = $values;
+ }
+
+ public function getValues()
+ {
+ return $this->values;
+ }
+
+ protected function finalizeValue($value)
+ {
+ $value = parent::finalizeValue($value);
+
+ if (!in_array($value, $this->values, true)) {
+ $ex = new InvalidConfigurationException(sprintf(
+ 'The value %s is not allowed for path "%s". Permissible values: %s',
+ json_encode($value),
+ $this->getPath(),
+ implode(', ', array_map('json_encode', $this->values))));
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+
+ return $value;
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Exception/DuplicateKeyException.php b/console/skel/symfony/config/Definition/Exception/DuplicateKeyException.php
new file mode 100644
index 0000000..48dd932
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Exception/DuplicateKeyException.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Exception;
+
+/**
+ * This exception is thrown whenever the key of an array is not unique. This can
+ * only be the case if the configuration is coming from an XML file.
+ *
+ * @author Johannes M. Schmitt
+ */
+class DuplicateKeyException extends InvalidConfigurationException
+{
+}
diff --git a/console/skel/symfony/config/Definition/Exception/Exception.php b/console/skel/symfony/config/Definition/Exception/Exception.php
new file mode 100644
index 0000000..8933a49
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Exception/Exception.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Exception;
+
+/**
+ * Base exception for all configuration exceptions.
+ *
+ * @author Johannes M. Schmitt
+ */
+class Exception extends \RuntimeException
+{
+}
diff --git a/console/skel/symfony/config/Definition/Exception/ForbiddenOverwriteException.php b/console/skel/symfony/config/Definition/Exception/ForbiddenOverwriteException.php
new file mode 100644
index 0000000..726c07f
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Exception/ForbiddenOverwriteException.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Exception;
+
+/**
+ * This exception is thrown when a configuration path is overwritten from a
+ * subsequent configuration file, but the entry node specifically forbids this.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ForbiddenOverwriteException extends InvalidConfigurationException
+{
+}
diff --git a/console/skel/symfony/config/Definition/Exception/InvalidConfigurationException.php b/console/skel/symfony/config/Definition/Exception/InvalidConfigurationException.php
new file mode 100644
index 0000000..3dbc57b
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Exception/InvalidConfigurationException.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Exception;
+
+/**
+ * A very general exception which can be thrown whenever non of the more specific
+ * exceptions is suitable.
+ *
+ * @author Johannes M. Schmitt
+ */
+class InvalidConfigurationException extends Exception
+{
+ private $path;
+ private $containsHints = false;
+
+ public function setPath($path)
+ {
+ $this->path = $path;
+ }
+
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Adds extra information that is suffixed to the original exception message.
+ *
+ * @param string $hint
+ */
+ public function addHint($hint)
+ {
+ if (!$this->containsHints) {
+ $this->message .= "\nHint: ".$hint;
+ $this->containsHints = true;
+ } else {
+ $this->message .= ', '.$hint;
+ }
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Exception/InvalidDefinitionException.php b/console/skel/symfony/config/Definition/Exception/InvalidDefinitionException.php
new file mode 100644
index 0000000..98310da
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Exception/InvalidDefinitionException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Exception;
+
+/**
+ * Thrown when an error is detected in a node Definition.
+ *
+ * @author Victor Berchet
+ */
+class InvalidDefinitionException extends Exception
+{
+}
diff --git a/console/skel/symfony/config/Definition/Exception/InvalidTypeException.php b/console/skel/symfony/config/Definition/Exception/InvalidTypeException.php
new file mode 100644
index 0000000..d7ca8c9
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Exception/InvalidTypeException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Exception;
+
+/**
+ * This exception is thrown if an invalid type is encountered.
+ *
+ * @author Johannes M. Schmitt
+ */
+class InvalidTypeException extends InvalidConfigurationException
+{
+}
diff --git a/console/skel/symfony/config/Definition/Exception/UnsetKeyException.php b/console/skel/symfony/config/Definition/Exception/UnsetKeyException.php
new file mode 100644
index 0000000..863181a
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Exception/UnsetKeyException.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Exception;
+
+/**
+ * This exception is usually not encountered by the end-user, but only used
+ * internally to signal the parent scope to unset a key.
+ *
+ * @author Johannes M. Schmitt
+ */
+class UnsetKeyException extends Exception
+{
+}
diff --git a/console/skel/symfony/config/Definition/FloatNode.php b/console/skel/symfony/config/Definition/FloatNode.php
new file mode 100644
index 0000000..5e1af17
--- /dev/null
+++ b/console/skel/symfony/config/Definition/FloatNode.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
+
+/**
+ * This node represents a float value in the config tree.
+ *
+ * @author Jeanmonod David
+ */
+class FloatNode extends NumericNode
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function validateType($value)
+ {
+ // Integers are also accepted, we just cast them
+ if (is_int($value)) {
+ $value = (float) $value;
+ }
+
+ if (!is_float($value)) {
+ $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected float, but got %s.', $this->getPath(), gettype($value)));
+ if ($hint = $this->getInfo()) {
+ $ex->addHint($hint);
+ }
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+ }
+}
diff --git a/console/skel/symfony/config/Definition/IntegerNode.php b/console/skel/symfony/config/Definition/IntegerNode.php
new file mode 100644
index 0000000..ba23070
--- /dev/null
+++ b/console/skel/symfony/config/Definition/IntegerNode.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
+
+/**
+ * This node represents an integer value in the config tree.
+ *
+ * @author Jeanmonod David
+ */
+class IntegerNode extends NumericNode
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function validateType($value)
+ {
+ if (!is_int($value)) {
+ $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected int, but got %s.', $this->getPath(), gettype($value)));
+ if ($hint = $this->getInfo()) {
+ $ex->addHint($hint);
+ }
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+ }
+}
diff --git a/console/skel/symfony/config/Definition/NodeInterface.php b/console/skel/symfony/config/Definition/NodeInterface.php
new file mode 100644
index 0000000..b9bddc4
--- /dev/null
+++ b/console/skel/symfony/config/Definition/NodeInterface.php
@@ -0,0 +1,88 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+/**
+ * Common Interface among all nodes.
+ *
+ * In most cases, it is better to inherit from BaseNode instead of implementing
+ * this interface yourself.
+ *
+ * @author Johannes M. Schmitt
+ */
+interface NodeInterface
+{
+ /**
+ * Returns the name of the node.
+ *
+ * @return string The name of the node
+ */
+ public function getName();
+
+ /**
+ * Returns the path of the node.
+ *
+ * @return string The node path
+ */
+ public function getPath();
+
+ /**
+ * Returns true when the node is required.
+ *
+ * @return bool If the node is required
+ */
+ public function isRequired();
+
+ /**
+ * Returns true when the node has a default value.
+ *
+ * @return bool If the node has a default value
+ */
+ public function hasDefaultValue();
+
+ /**
+ * Returns the default value of the node.
+ *
+ * @return mixed The default value
+ *
+ * @throws \RuntimeException if the node has no default value
+ */
+ public function getDefaultValue();
+
+ /**
+ * Normalizes the supplied value.
+ *
+ * @param mixed $value The value to normalize
+ *
+ * @return mixed The normalized value
+ */
+ public function normalize($value);
+
+ /**
+ * Merges two values together.
+ *
+ * @param mixed $leftSide
+ * @param mixed $rightSide
+ *
+ * @return mixed The merged values
+ */
+ public function merge($leftSide, $rightSide);
+
+ /**
+ * Finalizes a value.
+ *
+ * @param mixed $value The value to finalize
+ *
+ * @return mixed The finalized value
+ */
+ public function finalize($value);
+}
diff --git a/console/skel/symfony/config/Definition/NumericNode.php b/console/skel/symfony/config/Definition/NumericNode.php
new file mode 100644
index 0000000..439935e
--- /dev/null
+++ b/console/skel/symfony/config/Definition/NumericNode.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+
+/**
+ * This node represents a numeric value in the config tree.
+ *
+ * @author David Jeanmonod
+ */
+class NumericNode extends ScalarNode
+{
+ protected $min;
+ protected $max;
+
+ public function __construct($name, NodeInterface $parent = null, $min = null, $max = null)
+ {
+ parent::__construct($name, $parent);
+ $this->min = $min;
+ $this->max = $max;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function finalizeValue($value)
+ {
+ $value = parent::finalizeValue($value);
+
+ $errorMsg = null;
+ if (isset($this->min) && $value < $this->min) {
+ $errorMsg = sprintf('The value %s is too small for path "%s". Should be greater than or equal to %s', $value, $this->getPath(), $this->min);
+ }
+ if (isset($this->max) && $value > $this->max) {
+ $errorMsg = sprintf('The value %s is too big for path "%s". Should be less than or equal to %s', $value, $this->getPath(), $this->max);
+ }
+ if (isset($errorMsg)) {
+ $ex = new InvalidConfigurationException($errorMsg);
+ $ex->setPath($this->getPath());
+ throw $ex;
+ }
+
+ return $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function isValueEmpty($value)
+ {
+ // a numeric value cannot be empty
+ return false;
+ }
+}
diff --git a/console/skel/symfony/config/Definition/Processor.php b/console/skel/symfony/config/Definition/Processor.php
new file mode 100644
index 0000000..025e693
--- /dev/null
+++ b/console/skel/symfony/config/Definition/Processor.php
@@ -0,0 +1,97 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+/**
+ * This class is the entry point for config normalization/merging/finalization.
+ *
+ * @author Johannes M. Schmitt
+ */
+class Processor
+{
+ /**
+ * Processes an array of configurations.
+ *
+ * @param NodeInterface $configTree The node tree describing the configuration
+ * @param array $configs An array of configuration items to process
+ *
+ * @return array The processed configuration
+ */
+ public function process(NodeInterface $configTree, array $configs)
+ {
+ $currentConfig = array();
+ foreach ($configs as $config) {
+ $config = $configTree->normalize($config);
+ $currentConfig = $configTree->merge($currentConfig, $config);
+ }
+
+ return $configTree->finalize($currentConfig);
+ }
+
+ /**
+ * Processes an array of configurations.
+ *
+ * @param ConfigurationInterface $configuration The configuration class
+ * @param array $configs An array of configuration items to process
+ *
+ * @return array The processed configuration
+ */
+ public function processConfiguration(ConfigurationInterface $configuration, array $configs)
+ {
+ return $this->process($configuration->getConfigTreeBuilder()->buildTree(), $configs);
+ }
+
+ /**
+ * Normalizes a configuration entry.
+ *
+ * This method returns a normalize configuration array for a given key
+ * to remove the differences due to the original format (YAML and XML mainly).
+ *
+ * Here is an example.
+ *
+ * The configuration in XML:
+ *
+ * twig.extension.foo
+ * twig.extension.bar
+ *
+ * And the same configuration in YAML:
+ *
+ * extensions: ['twig.extension.foo', 'twig.extension.bar']
+ *
+ * @param array $config A config array
+ * @param string $key The key to normalize
+ * @param string $plural The plural form of the key if it is irregular
+ *
+ * @return array
+ */
+ public static function normalizeConfig($config, $key, $plural = null)
+ {
+ if (null === $plural) {
+ $plural = $key.'s';
+ }
+
+ if (isset($config[$plural])) {
+ return $config[$plural];
+ }
+
+ if (isset($config[$key])) {
+ if (is_string($config[$key]) || !is_int(key($config[$key]))) {
+ // only one
+ return array($config[$key]);
+ }
+
+ return $config[$key];
+ }
+
+ return array();
+ }
+}
diff --git a/console/skel/symfony/config/Definition/PrototypeNodeInterface.php b/console/skel/symfony/config/Definition/PrototypeNodeInterface.php
new file mode 100644
index 0000000..8bbb84d
--- /dev/null
+++ b/console/skel/symfony/config/Definition/PrototypeNodeInterface.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+/**
+ * This interface must be implemented by nodes which can be used as prototypes.
+ *
+ * @author Johannes M. Schmitt
+ */
+interface PrototypeNodeInterface extends NodeInterface
+{
+ /**
+ * Sets the name of the node.
+ *
+ * @param string $name The name of the node
+ */
+ public function setName($name);
+}
diff --git a/console/skel/symfony/config/Definition/PrototypedArrayNode.php b/console/skel/symfony/config/Definition/PrototypedArrayNode.php
new file mode 100644
index 0000000..1c3c218
--- /dev/null
+++ b/console/skel/symfony/config/Definition/PrototypedArrayNode.php
@@ -0,0 +1,389 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+use Symfony\Component\Config\Definition\Exception\DuplicateKeyException;
+use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
+use Symfony\Component\Config\Definition\Exception\Exception;
+
+/**
+ * Represents a prototyped Array node in the config tree.
+ *
+ * @author Johannes M. Schmitt
+ */
+class PrototypedArrayNode extends ArrayNode
+{
+ protected $prototype;
+ protected $keyAttribute;
+ protected $removeKeyAttribute = false;
+ protected $minNumberOfElements = 0;
+ protected $defaultValue = array();
+ protected $defaultChildren;
+ /**
+ * @var NodeInterface[] An array of the prototypes of the simplified value children
+ */
+ private $valuePrototypes = array();
+
+ /**
+ * Sets the minimum number of elements that a prototype based node must
+ * contain. By default this is zero, meaning no elements.
+ *
+ * @param int $number
+ */
+ public function setMinNumberOfElements($number)
+ {
+ $this->minNumberOfElements = $number;
+ }
+
+ /**
+ * Sets the attribute which value is to be used as key.
+ *
+ * This is useful when you have an indexed array that should be an
+ * associative array. You can select an item from within the array
+ * to be the key of the particular item. For example, if "id" is the
+ * "key", then:
+ *
+ * array(
+ * array('id' => 'my_name', 'foo' => 'bar'),
+ * );
+ *
+ * becomes
+ *
+ * array(
+ * 'my_name' => array('foo' => 'bar'),
+ * );
+ *
+ * If you'd like "'id' => 'my_name'" to still be present in the resulting
+ * array, then you can set the second argument of this method to false.
+ *
+ * @param string $attribute The name of the attribute which value is to be used as a key
+ * @param bool $remove Whether or not to remove the key
+ */
+ public function setKeyAttribute($attribute, $remove = true)
+ {
+ $this->keyAttribute = $attribute;
+ $this->removeKeyAttribute = $remove;
+ }
+
+ /**
+ * Retrieves the name of the attribute which value should be used as key.
+ *
+ * @return string The name of the attribute
+ */
+ public function getKeyAttribute()
+ {
+ return $this->keyAttribute;
+ }
+
+ /**
+ * Sets the default value of this node.
+ *
+ * @param string $value
+ *
+ * @throws \InvalidArgumentException if the default value is not an array
+ */
+ public function setDefaultValue($value)
+ {
+ if (!is_array($value)) {
+ throw new \InvalidArgumentException($this->getPath().': the default value of an array node has to be an array.');
+ }
+
+ $this->defaultValue = $value;
+ }
+
+ /**
+ * Checks if the node has a default value.
+ *
+ * @return bool
+ */
+ public function hasDefaultValue()
+ {
+ return true;
+ }
+
+ /**
+ * Adds default children when none are set.
+ *
+ * @param int|string|array|null $children The number of children|The child name|The children names to be added
+ */
+ public function setAddChildrenIfNoneSet($children = array('defaults'))
+ {
+ if (null === $children) {
+ $this->defaultChildren = array('defaults');
+ } else {
+ $this->defaultChildren = is_int($children) && $children > 0 ? range(1, $children) : (array) $children;
+ }
+ }
+
+ /**
+ * Retrieves the default value.
+ *
+ * The default value could be either explicited or derived from the prototype
+ * default value.
+ *
+ * @return array The default value
+ */
+ public function getDefaultValue()
+ {
+ if (null !== $this->defaultChildren) {
+ $default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : array();
+ $defaults = array();
+ foreach (array_values($this->defaultChildren) as $i => $name) {
+ $defaults[null === $this->keyAttribute ? $i : $name] = $default;
+ }
+
+ return $defaults;
+ }
+
+ return $this->defaultValue;
+ }
+
+ /**
+ * Sets the node prototype.
+ *
+ * @param PrototypeNodeInterface $node
+ */
+ public function setPrototype(PrototypeNodeInterface $node)
+ {
+ $this->prototype = $node;
+ }
+
+ /**
+ * Retrieves the prototype.
+ *
+ * @return PrototypeNodeInterface The prototype
+ */
+ public function getPrototype()
+ {
+ return $this->prototype;
+ }
+
+ /**
+ * Disable adding concrete children for prototyped nodes.
+ *
+ * @param NodeInterface $node The child node to add
+ *
+ * @throws Exception
+ */
+ public function addChild(NodeInterface $node)
+ {
+ throw new Exception('A prototyped array node can not have concrete children.');
+ }
+
+ /**
+ * Finalizes the value of this node.
+ *
+ * @param mixed $value
+ *
+ * @return mixed The finalized value
+ *
+ * @throws UnsetKeyException
+ * @throws InvalidConfigurationException if the node doesn't have enough children
+ */
+ protected function finalizeValue($value)
+ {
+ if (false === $value) {
+ $msg = sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value));
+ throw new UnsetKeyException($msg);
+ }
+
+ foreach ($value as $k => $v) {
+ $prototype = $this->getPrototypeForChild($k);
+ try {
+ $value[$k] = $prototype->finalize($v);
+ } catch (UnsetKeyException $e) {
+ unset($value[$k]);
+ }
+ }
+
+ if (count($value) < $this->minNumberOfElements) {
+ $msg = sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements);
+ $ex = new InvalidConfigurationException($msg);
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Normalizes the value.
+ *
+ * @param mixed $value The value to normalize
+ *
+ * @return mixed The normalized value
+ *
+ * @throws InvalidConfigurationException
+ * @throws DuplicateKeyException
+ */
+ protected function normalizeValue($value)
+ {
+ if (false === $value) {
+ return $value;
+ }
+
+ $value = $this->remapXml($value);
+
+ $isAssoc = array_keys($value) !== range(0, count($value) - 1);
+ $normalized = array();
+ foreach ($value as $k => $v) {
+ if (null !== $this->keyAttribute && is_array($v)) {
+ if (!isset($v[$this->keyAttribute]) && is_int($k) && !$isAssoc) {
+ $msg = sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath());
+ $ex = new InvalidConfigurationException($msg);
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ } elseif (isset($v[$this->keyAttribute])) {
+ $k = $v[$this->keyAttribute];
+
+ // remove the key attribute when required
+ if ($this->removeKeyAttribute) {
+ unset($v[$this->keyAttribute]);
+ }
+
+ // if only "value" is left
+ if (array_keys($v) === array('value')) {
+ $v = $v['value'];
+ if ($this->prototype instanceof ArrayNode && ($children = $this->prototype->getChildren()) && array_key_exists('value', $children)) {
+ $valuePrototype = current($this->valuePrototypes) ?: clone $children['value'];
+ $valuePrototype->parent = $this;
+ $originalClosures = $this->prototype->normalizationClosures;
+ if (is_array($originalClosures)) {
+ $valuePrototypeClosures = $valuePrototype->normalizationClosures;
+ $valuePrototype->normalizationClosures = is_array($valuePrototypeClosures) ? array_merge($originalClosures, $valuePrototypeClosures) : $originalClosures;
+ }
+ $this->valuePrototypes[$k] = $valuePrototype;
+ }
+ }
+ }
+
+ if (array_key_exists($k, $normalized)) {
+ $msg = sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath());
+ $ex = new DuplicateKeyException($msg);
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+ }
+
+ $prototype = $this->getPrototypeForChild($k);
+ if (null !== $this->keyAttribute || $isAssoc) {
+ $normalized[$k] = $prototype->normalize($v);
+ } else {
+ $normalized[] = $prototype->normalize($v);
+ }
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * Merges values together.
+ *
+ * @param mixed $leftSide The left side to merge
+ * @param mixed $rightSide The right side to merge
+ *
+ * @return mixed The merged values
+ *
+ * @throws InvalidConfigurationException
+ * @throws \RuntimeException
+ */
+ protected function mergeValues($leftSide, $rightSide)
+ {
+ if (false === $rightSide) {
+ // if this is still false after the last config has been merged the
+ // finalization pass will take care of removing this key entirely
+ return false;
+ }
+
+ if (false === $leftSide || !$this->performDeepMerging) {
+ return $rightSide;
+ }
+
+ foreach ($rightSide as $k => $v) {
+ // prototype, and key is irrelevant, so simply append the element
+ if (null === $this->keyAttribute) {
+ $leftSide[] = $v;
+ continue;
+ }
+
+ // no conflict
+ if (!array_key_exists($k, $leftSide)) {
+ if (!$this->allowNewKeys) {
+ $ex = new InvalidConfigurationException(sprintf(
+ 'You are not allowed to define new elements for path "%s". '.
+ 'Please define all elements for this path in one config file.',
+ $this->getPath()
+ ));
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+
+ $leftSide[$k] = $v;
+ continue;
+ }
+
+ $prototype = $this->getPrototypeForChild($k);
+ $leftSide[$k] = $prototype->merge($leftSide[$k], $v);
+ }
+
+ return $leftSide;
+ }
+
+ /**
+ * Returns a prototype for the child node that is associated to $key in the value array.
+ * For general child nodes, this will be $this->prototype.
+ * But if $this->removeKeyAttribute is true and there are only two keys in the child node:
+ * one is same as this->keyAttribute and the other is 'value', then the prototype will be different.
+ *
+ * For example, assume $this->keyAttribute is 'name' and the value array is as follows:
+ * array(
+ * array(
+ * 'name' => 'name001',
+ * 'value' => 'value001'
+ * )
+ * )
+ *
+ * Now, the key is 0 and the child node is:
+ * array(
+ * 'name' => 'name001',
+ * 'value' => 'value001'
+ * )
+ *
+ * When normalizing the value array, the 'name' element will removed from the child node
+ * and its value becomes the new key of the child node:
+ * array(
+ * 'name001' => array('value' => 'value001')
+ * )
+ *
+ * Now only 'value' element is left in the child node which can be further simplified into a string:
+ * array('name001' => 'value001')
+ *
+ * Now, the key becomes 'name001' and the child node becomes 'value001' and
+ * the prototype of child node 'name001' should be a ScalarNode instead of an ArrayNode instance.
+ *
+ * @param string $key The key of the child node
+ *
+ * @return mixed The prototype instance
+ */
+ private function getPrototypeForChild($key)
+ {
+ $prototype = isset($this->valuePrototypes[$key]) ? $this->valuePrototypes[$key] : $this->prototype;
+ $prototype->setName($key);
+
+ return $prototype;
+ }
+}
diff --git a/console/skel/symfony/config/Definition/ScalarNode.php b/console/skel/symfony/config/Definition/ScalarNode.php
new file mode 100644
index 0000000..6b3fd0b
--- /dev/null
+++ b/console/skel/symfony/config/Definition/ScalarNode.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
+
+/**
+ * This node represents a scalar value in the config tree.
+ *
+ * The following values are considered scalars:
+ * * booleans
+ * * strings
+ * * null
+ * * integers
+ * * floats
+ *
+ * @author Johannes M. Schmitt
+ */
+class ScalarNode extends VariableNode
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function validateType($value)
+ {
+ if (!is_scalar($value) && null !== $value) {
+ $ex = new InvalidTypeException(sprintf(
+ 'Invalid type for path "%s". Expected scalar, but got %s.',
+ $this->getPath(),
+ gettype($value)
+ ));
+ if ($hint = $this->getInfo()) {
+ $ex->addHint($hint);
+ }
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function isValueEmpty($value)
+ {
+ return null === $value || '' === $value;
+ }
+}
diff --git a/console/skel/symfony/config/Definition/VariableNode.php b/console/skel/symfony/config/Definition/VariableNode.php
new file mode 100644
index 0000000..a9c3528
--- /dev/null
+++ b/console/skel/symfony/config/Definition/VariableNode.php
@@ -0,0 +1,135 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+
+/**
+ * This node represents a value of variable type in the config tree.
+ *
+ * This node is intended for values of arbitrary type.
+ * Any PHP type is accepted as a value.
+ *
+ * @author Jeremy Mikola
+ */
+class VariableNode extends BaseNode implements PrototypeNodeInterface
+{
+ protected $defaultValueSet = false;
+ protected $defaultValue;
+ protected $allowEmptyValue = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setDefaultValue($value)
+ {
+ $this->defaultValueSet = true;
+ $this->defaultValue = $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasDefaultValue()
+ {
+ return $this->defaultValueSet;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDefaultValue()
+ {
+ $v = $this->defaultValue;
+
+ return $v instanceof \Closure ? $v() : $v;
+ }
+
+ /**
+ * Sets if this node is allowed to have an empty value.
+ *
+ * @param bool $boolean True if this entity will accept empty values
+ */
+ public function setAllowEmptyValue($boolean)
+ {
+ $this->allowEmptyValue = (bool) $boolean;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function validateType($value)
+ {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function finalizeValue($value)
+ {
+ if (!$this->allowEmptyValue && $this->isValueEmpty($value)) {
+ $ex = new InvalidConfigurationException(sprintf(
+ 'The path "%s" cannot contain an empty value, but got %s.',
+ $this->getPath(),
+ json_encode($value)
+ ));
+ if ($hint = $this->getInfo()) {
+ $ex->addHint($hint);
+ }
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+
+ return $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function normalizeValue($value)
+ {
+ return $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function mergeValues($leftSide, $rightSide)
+ {
+ return $rightSide;
+ }
+
+ /**
+ * Evaluates if the given value is to be treated as empty.
+ *
+ * By default, PHP's empty() function is used to test for emptiness. This
+ * method may be overridden by subtypes to better match their understanding
+ * of empty data.
+ *
+ * @param mixed $value
+ *
+ * @return bool
+ */
+ protected function isValueEmpty($value)
+ {
+ return empty($value);
+ }
+}
diff --git a/console/skel/symfony/config/DependencyInjection/ConfigCachePass.php b/console/skel/symfony/config/DependencyInjection/ConfigCachePass.php
new file mode 100644
index 0000000..02cae0d
--- /dev/null
+++ b/console/skel/symfony/config/DependencyInjection/ConfigCachePass.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * Adds services tagged config_cache.resource_checker to the config_cache_factory service, ordering them by priority.
+ *
+ * @author Matthias Pigulla
+ * @author Benjamin Klotz
+ */
+class ConfigCachePass implements CompilerPassInterface
+{
+ use PriorityTaggedServiceTrait;
+
+ private $factoryServiceId;
+ private $resourceCheckerTag;
+
+ public function __construct($factoryServiceId = 'config_cache_factory', $resourceCheckerTag = 'config_cache.resource_checker')
+ {
+ $this->factoryServiceId = $factoryServiceId;
+ $this->resourceCheckerTag = $resourceCheckerTag;
+ }
+
+ public function process(ContainerBuilder $container)
+ {
+ $resourceCheckers = $this->findAndSortTaggedServices($this->resourceCheckerTag, $container);
+
+ if (empty($resourceCheckers)) {
+ return;
+ }
+
+ $container->getDefinition($this->factoryServiceId)->replaceArgument(0, new IteratorArgument($resourceCheckers));
+ }
+}
diff --git a/console/skel/symfony/config/Exception/FileLoaderImportCircularReferenceException.php b/console/skel/symfony/config/Exception/FileLoaderImportCircularReferenceException.php
new file mode 100644
index 0000000..6a3b01c
--- /dev/null
+++ b/console/skel/symfony/config/Exception/FileLoaderImportCircularReferenceException.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Exception;
+
+/**
+ * Exception class for when a circular reference is detected when importing resources.
+ *
+ * @author Fabien Potencier
+ */
+class FileLoaderImportCircularReferenceException extends FileLoaderLoadException
+{
+ public function __construct(array $resources, $code = null, $previous = null)
+ {
+ $message = sprintf('Circular reference detected in "%s" ("%s" > "%s").', $this->varToString($resources[0]), implode('" > "', $resources), $resources[0]);
+
+ \Exception::__construct($message, $code, $previous);
+ }
+}
diff --git a/console/skel/symfony/config/Exception/FileLoaderLoadException.php b/console/skel/symfony/config/Exception/FileLoaderLoadException.php
new file mode 100644
index 0000000..564f75c
--- /dev/null
+++ b/console/skel/symfony/config/Exception/FileLoaderLoadException.php
@@ -0,0 +1,109 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Exception;
+
+/**
+ * Exception class for when a resource cannot be loaded or imported.
+ *
+ * @author Ryan Weaver
+ */
+class FileLoaderLoadException extends \Exception
+{
+ /**
+ * @param string $resource The resource that could not be imported
+ * @param string $sourceResource The original resource importing the new resource
+ * @param int $code The error code
+ * @param \Exception $previous A previous exception
+ * @param string $type The type of resource
+ */
+ public function __construct($resource, $sourceResource = null, $code = null, $previous = null, $type = null)
+ {
+ $message = '';
+ if ($previous) {
+ // Include the previous exception, to help the user see what might be the underlying cause
+
+ // Trim the trailing period of the previous message. We only want 1 period remove so no rtrim...
+ if ('.' === substr($previous->getMessage(), -1)) {
+ $trimmedMessage = substr($previous->getMessage(), 0, -1);
+ $message .= sprintf('%s', $trimmedMessage).' in ';
+ } else {
+ $message .= sprintf('%s', $previous->getMessage()).' in ';
+ }
+ $message .= $resource.' ';
+
+ // show tweaked trace to complete the human readable sentence
+ if (null === $sourceResource) {
+ $message .= sprintf('(which is loaded in resource "%s")', $this->varToString($resource));
+ } else {
+ $message .= sprintf('(which is being imported from "%s")', $this->varToString($sourceResource));
+ }
+ $message .= '.';
+
+ // if there's no previous message, present it the default way
+ } elseif (null === $sourceResource) {
+ $message .= sprintf('Cannot load resource "%s".', $this->varToString($resource));
+ } else {
+ $message .= sprintf('Cannot import resource "%s" from "%s".', $this->varToString($resource), $this->varToString($sourceResource));
+ }
+
+ // Is the resource located inside a bundle?
+ if ('@' === $resource[0]) {
+ $parts = explode(DIRECTORY_SEPARATOR, $resource);
+ $bundle = substr($parts[0], 1);
+ $message .= sprintf(' Make sure the "%s" bundle is correctly registered and loaded in the application kernel class.', $bundle);
+ $message .= sprintf(' If the bundle is registered, make sure the bundle path "%s" is not empty.', $resource);
+ } elseif (null !== $type) {
+ // maybe there is no loader for this specific type
+ if ('annotation' === $type) {
+ $message .= ' Make sure annotations are enabled.';
+ } else {
+ $message .= sprintf(' Make sure there is a loader supporting the "%s" type.', $type);
+ }
+ }
+
+ parent::__construct($message, $code, $previous);
+ }
+
+ protected function varToString($var)
+ {
+ if (is_object($var)) {
+ return sprintf('Object(%s)', get_class($var));
+ }
+
+ if (is_array($var)) {
+ $a = array();
+ foreach ($var as $k => $v) {
+ $a[] = sprintf('%s => %s', $k, $this->varToString($v));
+ }
+
+ return sprintf('Array(%s)', implode(', ', $a));
+ }
+
+ if (is_resource($var)) {
+ return sprintf('Resource(%s)', get_resource_type($var));
+ }
+
+ if (null === $var) {
+ return 'null';
+ }
+
+ if (false === $var) {
+ return 'false';
+ }
+
+ if (true === $var) {
+ return 'true';
+ }
+
+ return (string) $var;
+ }
+}
diff --git a/console/skel/symfony/config/Exception/FileLocatorFileNotFoundException.php b/console/skel/symfony/config/Exception/FileLocatorFileNotFoundException.php
new file mode 100644
index 0000000..af764eb
--- /dev/null
+++ b/console/skel/symfony/config/Exception/FileLocatorFileNotFoundException.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Exception;
+
+/**
+ * File locator exception if a file does not exist.
+ *
+ * @author Leo Feyer
+ */
+class FileLocatorFileNotFoundException extends \InvalidArgumentException
+{
+ private $paths;
+
+ public function __construct($message = '', $code = 0, $previous = null, array $paths = array())
+ {
+ parent::__construct($message, $code, $previous);
+
+ $this->paths = $paths;
+ }
+
+ public function getPaths()
+ {
+ return $this->paths;
+ }
+}
diff --git a/console/skel/symfony/config/FileLocator.php b/console/skel/symfony/config/FileLocator.php
new file mode 100644
index 0000000..9fc1924
--- /dev/null
+++ b/console/skel/symfony/config/FileLocator.php
@@ -0,0 +1,100 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
+
+/**
+ * FileLocator uses an array of pre-defined paths to find files.
+ *
+ * @author Fabien Potencier
+ */
+class FileLocator implements FileLocatorInterface
+{
+ protected $paths;
+
+ /**
+ * Constructor.
+ *
+ * @param string|array $paths A path or an array of paths where to look for resources
+ */
+ public function __construct($paths = array())
+ {
+ $this->paths = (array) $paths;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function locate(string $name, string $currentPath = null, bool $first = true)
+ {
+ if ('' == $name) {
+ throw new \InvalidArgumentException('An empty file name is not valid to be located.');
+ }
+
+ if ($this->isAbsolutePath($name)) {
+ if (!file_exists($name)) {
+ throw new FileLocatorFileNotFoundException(sprintf('The file "%s" does not exist.', $name), 0, null, array($name));
+ }
+
+ return $name;
+ }
+
+ $paths = $this->paths;
+
+ if (null !== $currentPath) {
+ array_unshift($paths, $currentPath);
+ }
+
+ $paths = array_unique($paths);
+ $filepaths = $notfound = array();
+
+ foreach ($paths as $path) {
+ if (@file_exists($file = $path.DIRECTORY_SEPARATOR.$name)) {
+ if (true === $first) {
+ return $file;
+ }
+ $filepaths[] = $file;
+ } else {
+ $notfound[] = $file;
+ }
+ }
+
+ if (!$filepaths) {
+ throw new FileLocatorFileNotFoundException(sprintf('The file "%s" does not exist (in: %s).', $name, implode(', ', $paths)), 0, null, $notfound);
+ }
+
+ return $filepaths;
+ }
+
+ /**
+ * Returns whether the file path is an absolute path.
+ *
+ * @param string $file A file path
+ *
+ * @return bool
+ */
+ private function isAbsolutePath($file)
+ {
+ if ($file[0] === '/' || $file[0] === '\\'
+ || (strlen($file) > 3 && ctype_alpha($file[0])
+ && $file[1] === ':'
+ && ($file[2] === '\\' || $file[2] === '/')
+ )
+ || null !== parse_url($file, PHP_URL_SCHEME)
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/console/skel/symfony/config/FileLocatorInterface.php b/console/skel/symfony/config/FileLocatorInterface.php
new file mode 100644
index 0000000..e639132
--- /dev/null
+++ b/console/skel/symfony/config/FileLocatorInterface.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
+
+/**
+ * @author Fabien Potencier
+ */
+interface FileLocatorInterface
+{
+ /**
+ * Returns a full path for a given file name.
+ *
+ * @param string $name The file name to locate
+ * @param string|null $currentPath The current path
+ * @param bool $first Whether to return the first occurrence or an array of filenames
+ *
+ * @return string|array The full path to the file or an array of file paths
+ *
+ * @throws \InvalidArgumentException If $name is empty
+ * @throws FileLocatorFileNotFoundException If a file is not found
+ */
+ public function locate(string $file, string $currentPath = null, bool $first = true);
+}
diff --git a/console/skel/symfony/config/LICENSE b/console/skel/symfony/config/LICENSE
new file mode 100644
index 0000000..17d16a1
--- /dev/null
+++ b/console/skel/symfony/config/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2017 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/console/skel/symfony/config/Loader/DelegatingLoader.php b/console/skel/symfony/config/Loader/DelegatingLoader.php
new file mode 100644
index 0000000..23b6256
--- /dev/null
+++ b/console/skel/symfony/config/Loader/DelegatingLoader.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Loader;
+
+use Symfony\Component\Config\Exception\FileLoaderLoadException;
+
+/**
+ * DelegatingLoader delegates loading to other loaders using a loader resolver.
+ *
+ * This loader acts as an array of LoaderInterface objects - each having
+ * a chance to load a given resource (handled by the resolver)
+ *
+ * @author Fabien Potencier
+ */
+class DelegatingLoader extends Loader
+{
+ /**
+ * Constructor.
+ *
+ * @param LoaderResolverInterface $resolver A LoaderResolverInterface instance
+ */
+ public function __construct(LoaderResolverInterface $resolver)
+ {
+ $this->resolver = $resolver;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function load($resource, $type = null)
+ {
+ if (false === $loader = $this->resolver->resolve($resource, $type)) {
+ throw new FileLoaderLoadException($resource, null, null, null, $type);
+ }
+
+ return $loader->load($resource, $type);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports($resource, $type = null)
+ {
+ return false !== $this->resolver->resolve($resource, $type);
+ }
+}
diff --git a/console/skel/symfony/config/Loader/FileLoader.php b/console/skel/symfony/config/Loader/FileLoader.php
new file mode 100644
index 0000000..02aa811
--- /dev/null
+++ b/console/skel/symfony/config/Loader/FileLoader.php
@@ -0,0 +1,183 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Loader;
+
+use Symfony\Component\Config\FileLocatorInterface;
+use Symfony\Component\Config\Exception\FileLoaderLoadException;
+use Symfony\Component\Config\Exception\FileLoaderImportCircularReferenceException;
+use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
+use Symfony\Component\Config\Resource\FileExistenceResource;
+use Symfony\Component\Config\Resource\GlobResource;
+
+/**
+ * FileLoader is the abstract class used by all built-in loaders that are file based.
+ *
+ * @author Fabien Potencier
+ */
+abstract class FileLoader extends Loader
+{
+ /**
+ * @var array
+ */
+ protected static $loading = array();
+
+ /**
+ * @var FileLocatorInterface
+ */
+ protected $locator;
+
+ private $currentDir;
+
+ /**
+ * Constructor.
+ *
+ * @param FileLocatorInterface $locator A FileLocatorInterface instance
+ */
+ public function __construct(FileLocatorInterface $locator)
+ {
+ $this->locator = $locator;
+ }
+
+ /**
+ * Sets the current directory.
+ *
+ * @param string $dir
+ */
+ public function setCurrentDir($dir)
+ {
+ $this->currentDir = $dir;
+ }
+
+ /**
+ * Returns the file locator used by this loader.
+ *
+ * @return FileLocatorInterface
+ */
+ public function getLocator()
+ {
+ return $this->locator;
+ }
+
+ /**
+ * Imports a resource.
+ *
+ * @param mixed $resource A Resource
+ * @param string|null $type The resource type or null if unknown
+ * @param bool $ignoreErrors Whether to ignore import errors or not
+ * @param string|null $sourceResource The original resource importing the new resource
+ *
+ * @return mixed
+ *
+ * @throws FileLoaderLoadException
+ * @throws FileLoaderImportCircularReferenceException
+ * @throws FileLocatorFileNotFoundException
+ */
+ public function import($resource, $type = null, $ignoreErrors = false, $sourceResource = null)
+ {
+ if (is_string($resource) && strlen($resource) !== $i = strcspn($resource, '*?{[')) {
+ $ret = array();
+ $isSubpath = 0 !== $i && false !== strpos(substr($resource, 0, $i), '/');
+ foreach ($this->glob($resource, false, $_, $ignoreErrors || !$isSubpath) as $path => $info) {
+ if (null !== $res = $this->doImport($path, $type, $ignoreErrors, $sourceResource)) {
+ $ret[] = $res;
+ }
+ $isSubpath = true;
+ }
+
+ if ($isSubpath) {
+ return isset($ret[1]) ? $ret : (isset($ret[0]) ? $ret[0] : null);
+ }
+ }
+
+ return $this->doImport($resource, $type, $ignoreErrors, $sourceResource);
+ }
+
+ /**
+ * @internal
+ */
+ protected function glob($pattern, $recursive, &$resource = null, $ignoreErrors = false)
+ {
+ if (strlen($pattern) === $i = strcspn($pattern, '*?{[')) {
+ $prefix = $pattern;
+ $pattern = '';
+ } elseif (0 === $i || false === strpos(substr($pattern, 0, $i), '/')) {
+ $prefix = '.';
+ $pattern = '/'.$pattern;
+ } else {
+ $prefix = dirname(substr($pattern, 0, 1 + $i));
+ $pattern = substr($pattern, strlen($prefix));
+ }
+
+ try {
+ $prefix = $this->locator->locate($prefix, $this->currentDir, true);
+ } catch (FileLocatorFileNotFoundException $e) {
+ if (!$ignoreErrors) {
+ throw $e;
+ }
+
+ $resource = array();
+ foreach ($e->getPaths() as $path) {
+ $resource[] = new FileExistenceResource($path);
+ }
+
+ return;
+ }
+ $resource = new GlobResource($prefix, $pattern, $recursive);
+
+ foreach ($resource as $path => $info) {
+ yield $path => $info;
+ }
+ }
+
+ private function doImport($resource, $type = null, $ignoreErrors = false, $sourceResource = null)
+ {
+ try {
+ $loader = $this->resolve($resource, $type);
+
+ if ($loader instanceof self && null !== $this->currentDir) {
+ $resource = $loader->getLocator()->locate($resource, $this->currentDir, false);
+ }
+
+ $resources = is_array($resource) ? $resource : array($resource);
+ for ($i = 0; $i < $resourcesCount = count($resources); ++$i) {
+ if (isset(self::$loading[$resources[$i]])) {
+ if ($i == $resourcesCount - 1) {
+ throw new FileLoaderImportCircularReferenceException(array_keys(self::$loading));
+ }
+ } else {
+ $resource = $resources[$i];
+ break;
+ }
+ }
+ self::$loading[$resource] = true;
+
+ try {
+ $ret = $loader->load($resource, $type);
+ } finally {
+ unset(self::$loading[$resource]);
+ }
+
+ return $ret;
+ } catch (FileLoaderImportCircularReferenceException $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ if (!$ignoreErrors) {
+ // prevent embedded imports from nesting multiple exceptions
+ if ($e instanceof FileLoaderLoadException) {
+ throw $e;
+ }
+
+ throw new FileLoaderLoadException($resource, $sourceResource, null, $e, $type);
+ }
+ }
+ }
+}
diff --git a/console/skel/symfony/config/Loader/GlobFileLoader.php b/console/skel/symfony/config/Loader/GlobFileLoader.php
new file mode 100644
index 0000000..f432b45
--- /dev/null
+++ b/console/skel/symfony/config/Loader/GlobFileLoader.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Loader;
+
+/**
+ * GlobFileLoader loads files from a glob pattern.
+ *
+ * @author Fabien Potencier
+ */
+class GlobFileLoader extends FileLoader
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function load($resource, $type = null)
+ {
+ return $this->import($resource);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports($resource, $type = null)
+ {
+ return 'glob' === $type;
+ }
+}
diff --git a/console/skel/symfony/config/Loader/Loader.php b/console/skel/symfony/config/Loader/Loader.php
new file mode 100644
index 0000000..d2f2ec9
--- /dev/null
+++ b/console/skel/symfony/config/Loader/Loader.php
@@ -0,0 +1,78 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Loader;
+
+use Symfony\Component\Config\Exception\FileLoaderLoadException;
+
+/**
+ * Loader is the abstract class used by all built-in loaders.
+ *
+ * @author Fabien Potencier
+ */
+abstract class Loader implements LoaderInterface
+{
+ protected $resolver;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getResolver()
+ {
+ return $this->resolver;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setResolver(LoaderResolverInterface $resolver)
+ {
+ $this->resolver = $resolver;
+ }
+
+ /**
+ * Imports a resource.
+ *
+ * @param mixed $resource A resource
+ * @param string|null $type The resource type or null if unknown
+ *
+ * @return mixed
+ */
+ public function import($resource, $type = null)
+ {
+ return $this->resolve($resource, $type)->load($resource, $type);
+ }
+
+ /**
+ * Finds a loader able to load an imported resource.
+ *
+ * @param mixed $resource A resource
+ * @param string|null $type The resource type or null if unknown
+ *
+ * @return $this|LoaderInterface
+ *
+ * @throws FileLoaderLoadException If no loader is found
+ */
+ public function resolve($resource, $type = null)
+ {
+ if ($this->supports($resource, $type)) {
+ return $this;
+ }
+
+ $loader = null === $this->resolver ? false : $this->resolver->resolve($resource, $type);
+
+ if (false === $loader) {
+ throw new FileLoaderLoadException($resource, null, null, null, $type);
+ }
+
+ return $loader;
+ }
+}
diff --git a/console/skel/symfony/config/Loader/LoaderInterface.php b/console/skel/symfony/config/Loader/LoaderInterface.php
new file mode 100644
index 0000000..a097322
--- /dev/null
+++ b/console/skel/symfony/config/Loader/LoaderInterface.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Loader;
+
+/**
+ * LoaderInterface is the interface implemented by all loader classes.
+ *
+ * @author Fabien Potencier
+ */
+interface LoaderInterface
+{
+ /**
+ * Loads a resource.
+ *
+ * @param mixed $resource The resource
+ * @param string|null $type The resource type or null if unknown
+ *
+ * @throws \Exception If something went wrong
+ */
+ public function load($resource, string $type = NULL);
+
+ /**
+ * Returns whether this class supports the given resource.
+ *
+ * @param mixed $resource A resource
+ * @param string|null $type The resource type or null if unknown
+ *
+ * @return bool True if this class supports the given resource, false otherwise
+ */
+ public function supports($resource, string $type = NULL);
+
+ /**
+ * Gets the loader resolver.
+ *
+ * @return LoaderResolverInterface A LoaderResolverInterface instance
+ */
+ public function getResolver();
+
+ /**
+ * Sets the loader resolver.
+ *
+ * @param LoaderResolverInterface $resolver A LoaderResolverInterface instance
+ */
+ public function setResolver(LoaderResolverInterface $resolver);
+}
diff --git a/console/skel/symfony/config/Loader/LoaderResolver.php b/console/skel/symfony/config/Loader/LoaderResolver.php
new file mode 100644
index 0000000..dc6846d
--- /dev/null
+++ b/console/skel/symfony/config/Loader/LoaderResolver.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Loader;
+
+/**
+ * LoaderResolver selects a loader for a given resource.
+ *
+ * A resource can be anything (e.g. a full path to a config file or a Closure).
+ * Each loader determines whether it can load a resource and how.
+ *
+ * @author Fabien Potencier
+ */
+class LoaderResolver implements LoaderResolverInterface
+{
+ /**
+ * @var LoaderInterface[] An array of LoaderInterface objects
+ */
+ private $loaders = array();
+
+ /**
+ * Constructor.
+ *
+ * @param LoaderInterface[] $loaders An array of loaders
+ */
+ public function __construct(array $loaders = array())
+ {
+ foreach ($loaders as $loader) {
+ $this->addLoader($loader);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function resolve($resource, $type = null)
+ {
+ foreach ($this->loaders as $loader) {
+ if ($loader->supports($resource, $type)) {
+ return $loader;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Adds a loader.
+ *
+ * @param LoaderInterface $loader A LoaderInterface instance
+ */
+ public function addLoader(LoaderInterface $loader)
+ {
+ $this->loaders[] = $loader;
+ $loader->setResolver($this);
+ }
+
+ /**
+ * Returns the registered loaders.
+ *
+ * @return LoaderInterface[] An array of LoaderInterface instances
+ */
+ public function getLoaders()
+ {
+ return $this->loaders;
+ }
+}
diff --git a/console/skel/symfony/config/Loader/LoaderResolverInterface.php b/console/skel/symfony/config/Loader/LoaderResolverInterface.php
new file mode 100644
index 0000000..40f1a1a
--- /dev/null
+++ b/console/skel/symfony/config/Loader/LoaderResolverInterface.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Loader;
+
+/**
+ * LoaderResolverInterface selects a loader for a given resource.
+ *
+ * @author Fabien Potencier
+ */
+interface LoaderResolverInterface
+{
+ /**
+ * Returns a loader able to load the resource.
+ *
+ * @param mixed $resource A resource
+ * @param string|null $type The resource type or null if unknown
+ *
+ * @return LoaderInterface|false The loader or false if none is able to load the resource
+ */
+ public function resolve($resource, $type = null);
+}
diff --git a/console/skel/symfony/config/README.md b/console/skel/symfony/config/README.md
new file mode 100644
index 0000000..bf400da
--- /dev/null
+++ b/console/skel/symfony/config/README.md
@@ -0,0 +1,15 @@
+Config Component
+================
+
+The Config component provides several classes to help you find, load, combine,
+autofill and validate configuration values of any kind, whatever their source
+may be (YAML, XML, INI files, or for instance a database).
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/config/index.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/console/skel/symfony/config/Resource/ClassExistenceResource.php b/console/skel/symfony/config/Resource/ClassExistenceResource.php
new file mode 100644
index 0000000..8a86a42
--- /dev/null
+++ b/console/skel/symfony/config/Resource/ClassExistenceResource.php
@@ -0,0 +1,145 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+/**
+ * ClassExistenceResource represents a class existence.
+ * Freshness is only evaluated against resource existence.
+ *
+ * The resource must be a fully-qualified class name.
+ *
+ * @author Fabien Potencier
+ */
+class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializable
+{
+ private $resource;
+ private $exists;
+
+ private static $autoloadLevel = 0;
+ private static $existsCache = array();
+
+ /**
+ * @param string $resource The fully-qualified class name
+ * @param bool|null $exists Boolean when the existency check has already been done
+ */
+ public function __construct($resource, $exists = null)
+ {
+ $this->resource = $resource;
+ if (null !== $exists) {
+ $this->exists = (bool) $exists;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __toString()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * @return string The file path to the resource
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isFresh($timestamp)
+ {
+ $loaded = class_exists($this->resource, false) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
+
+ if (null !== $exists = &self::$existsCache[$this->resource]) {
+ $exists = $exists || $loaded;
+ } elseif (!$exists = $loaded) {
+ if (!self::$autoloadLevel++) {
+ spl_autoload_register(__CLASS__.'::throwOnRequiredClass');
+ }
+
+ try {
+ $exists = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
+ } catch (\ReflectionException $e) {
+ $exists = false;
+ } finally {
+ if (!--self::$autoloadLevel) {
+ spl_autoload_unregister(__CLASS__.'::throwOnRequiredClass');
+ }
+ }
+ }
+
+ if (null === $this->exists) {
+ $this->exists = $exists;
+ }
+
+ return $this->exists xor !$exists;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function serialize()
+ {
+ if (null === $this->exists) {
+ $this->isFresh(0);
+ }
+
+ return serialize(array($this->resource, $this->exists));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unserialize($serialized)
+ {
+ list($this->resource, $this->exists) = unserialize($serialized);
+ }
+
+ /**
+ * @throws \ReflectionException When $class is not found and is required
+ */
+ private static function throwOnRequiredClass($class)
+ {
+ $e = new \ReflectionException("Class $class does not exist");
+ $trace = $e->getTrace();
+ $autoloadFrame = array(
+ 'function' => 'spl_autoload_call',
+ 'args' => array($class),
+ );
+ $i = 1 + array_search($autoloadFrame, $trace, true);
+
+ if (isset($trace[$i]['function']) && !isset($trace[$i]['class'])) {
+ switch ($trace[$i]['function']) {
+ case 'get_class_methods':
+ case 'get_class_vars':
+ case 'get_parent_class':
+ case 'is_a':
+ case 'is_subclass_of':
+ case 'class_exists':
+ case 'class_implements':
+ case 'class_parents':
+ case 'trait_exists':
+ case 'defined':
+ case 'interface_exists':
+ case 'method_exists':
+ case 'property_exists':
+ case 'is_callable':
+ return;
+ }
+ }
+
+ throw $e;
+ }
+}
diff --git a/console/skel/symfony/config/Resource/ComposerResource.php b/console/skel/symfony/config/Resource/ComposerResource.php
new file mode 100644
index 0000000..56224d1
--- /dev/null
+++ b/console/skel/symfony/config/Resource/ComposerResource.php
@@ -0,0 +1,94 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+/**
+ * ComposerResource tracks the PHP version and Composer dependencies.
+ *
+ * @author Nicolas Grekas
+ */
+class ComposerResource implements SelfCheckingResourceInterface, \Serializable
+{
+ private $versions;
+ private $vendors;
+
+ private static $runtimeVersion;
+ private static $runtimeVendors;
+
+ public function __construct()
+ {
+ self::refresh();
+ $this->versions = self::$runtimeVersion;
+ $this->vendors = self::$runtimeVendors;
+ }
+
+ public function getVendors()
+ {
+ return array_keys($this->vendors);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __toString()
+ {
+ return __CLASS__;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isFresh($timestamp)
+ {
+ self::refresh();
+
+ if (self::$runtimeVersion !== $this->versions) {
+ return false;
+ }
+
+ return self::$runtimeVendors === $this->vendors;
+ }
+
+ public function serialize()
+ {
+ return serialize(array($this->versions, $this->vendors));
+ }
+
+ public function unserialize($serialized)
+ {
+ list($this->versions, $this->vendors) = unserialize($serialized);
+ }
+
+ private static function refresh()
+ {
+ if (null !== self::$runtimeVersion) {
+ return;
+ }
+
+ self::$runtimeVersion = array();
+ self::$runtimeVendors = array();
+
+ foreach (get_loaded_extensions() as $ext) {
+ self::$runtimeVersion[$ext] = phpversion($ext);
+ }
+
+ foreach (get_declared_classes() as $class) {
+ if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
+ $r = new \ReflectionClass($class);
+ $v = dirname(dirname($r->getFileName()));
+ if (file_exists($v.'/composer/installed.json')) {
+ self::$runtimeVendors[$v] = @filemtime($v.'/composer/installed.json');
+ }
+ }
+ }
+ }
+}
diff --git a/console/skel/symfony/config/Resource/DirectoryResource.php b/console/skel/symfony/config/Resource/DirectoryResource.php
new file mode 100644
index 0000000..07280b4
--- /dev/null
+++ b/console/skel/symfony/config/Resource/DirectoryResource.php
@@ -0,0 +1,118 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+/**
+ * DirectoryResource represents a resources stored in a subdirectory tree.
+ *
+ * @author Fabien Potencier
+ */
+class DirectoryResource implements SelfCheckingResourceInterface, \Serializable
+{
+ private $resource;
+ private $pattern;
+
+ /**
+ * Constructor.
+ *
+ * @param string $resource The file path to the resource
+ * @param string|null $pattern A pattern to restrict monitored files
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function __construct($resource, $pattern = null)
+ {
+ $this->resource = realpath($resource) ?: (file_exists($resource) ? $resource : false);
+ $this->pattern = $pattern;
+
+ if (false === $this->resource || !is_dir($this->resource)) {
+ throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.', $resource));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __toString()
+ {
+ return md5(serialize(array($this->resource, $this->pattern)));
+ }
+
+ /**
+ * @return string The file path to the resource
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * Returns the pattern to restrict monitored files.
+ *
+ * @return string|null
+ */
+ public function getPattern()
+ {
+ return $this->pattern;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isFresh($timestamp)
+ {
+ if (!is_dir($this->resource)) {
+ return false;
+ }
+
+ if ($timestamp < filemtime($this->resource)) {
+ return false;
+ }
+
+ foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) as $file) {
+ // if regex filtering is enabled only check matching files
+ if ($this->pattern && $file->isFile() && !preg_match($this->pattern, $file->getBasename())) {
+ continue;
+ }
+
+ // always monitor directories for changes, except the .. entries
+ // (otherwise deleted files wouldn't get detected)
+ if ($file->isDir() && '/..' === substr($file, -3)) {
+ continue;
+ }
+
+ // for broken links
+ try {
+ $fileMTime = $file->getMTime();
+ } catch (\RuntimeException $e) {
+ continue;
+ }
+
+ // early return if a file's mtime exceeds the passed timestamp
+ if ($timestamp < $fileMTime) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public function serialize()
+ {
+ return serialize(array($this->resource, $this->pattern));
+ }
+
+ public function unserialize($serialized)
+ {
+ list($this->resource, $this->pattern) = unserialize($serialized);
+ }
+}
diff --git a/console/skel/symfony/config/Resource/FileExistenceResource.php b/console/skel/symfony/config/Resource/FileExistenceResource.php
new file mode 100644
index 0000000..349402e
--- /dev/null
+++ b/console/skel/symfony/config/Resource/FileExistenceResource.php
@@ -0,0 +1,78 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+/**
+ * FileExistenceResource represents a resource stored on the filesystem.
+ * Freshness is only evaluated against resource creation or deletion.
+ *
+ * The resource can be a file or a directory.
+ *
+ * @author Charles-Henri Bruyand
+ */
+class FileExistenceResource implements SelfCheckingResourceInterface, \Serializable
+{
+ private $resource;
+
+ private $exists;
+
+ /**
+ * Constructor.
+ *
+ * @param string $resource The file path to the resource
+ */
+ public function __construct($resource)
+ {
+ $this->resource = (string) $resource;
+ $this->exists = file_exists($resource);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __toString()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * @return string The file path to the resource
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isFresh($timestamp)
+ {
+ return file_exists($this->resource) === $this->exists;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function serialize()
+ {
+ return serialize(array($this->resource, $this->exists));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unserialize($serialized)
+ {
+ list($this->resource, $this->exists) = unserialize($serialized);
+ }
+}
diff --git a/console/skel/symfony/config/Resource/FileResource.php b/console/skel/symfony/config/Resource/FileResource.php
new file mode 100644
index 0000000..6ad1305
--- /dev/null
+++ b/console/skel/symfony/config/Resource/FileResource.php
@@ -0,0 +1,77 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+/**
+ * FileResource represents a resource stored on the filesystem.
+ *
+ * The resource can be a file or a directory.
+ *
+ * @author Fabien Potencier
+ */
+class FileResource implements SelfCheckingResourceInterface, \Serializable
+{
+ /**
+ * @var string|false
+ */
+ private $resource;
+
+ /**
+ * Constructor.
+ *
+ * @param string $resource The file path to the resource
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function __construct($resource)
+ {
+ $this->resource = realpath($resource) ?: (file_exists($resource) ? $resource : false);
+
+ if (false === $this->resource) {
+ throw new \InvalidArgumentException(sprintf('The file "%s" does not exist.', $resource));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __toString()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * @return string The canonicalized, absolute path to the resource
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isFresh($timestamp)
+ {
+ return file_exists($this->resource) && @filemtime($this->resource) <= $timestamp;
+ }
+
+ public function serialize()
+ {
+ return serialize($this->resource);
+ }
+
+ public function unserialize($serialized)
+ {
+ $this->resource = unserialize($serialized);
+ }
+}
diff --git a/console/skel/symfony/config/Resource/GlobResource.php b/console/skel/symfony/config/Resource/GlobResource.php
new file mode 100644
index 0000000..6762520
--- /dev/null
+++ b/console/skel/symfony/config/Resource/GlobResource.php
@@ -0,0 +1,153 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+use Symfony\Component\Finder\Finder;
+use Symfony\Component\Finder\Glob;
+
+/**
+ * GlobResource represents a set of resources stored on the filesystem.
+ *
+ * Only existence/removal is tracked (not mtimes.)
+ *
+ * @author Nicolas Grekas
+ */
+class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface, \Serializable
+{
+ private $prefix;
+ private $pattern;
+ private $recursive;
+ private $hash;
+
+ /**
+ * Constructor.
+ *
+ * @param string $prefix A directory prefix
+ * @param string $pattern A glob pattern
+ * @param bool $recursive Whether directories should be scanned recursively or not
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function __construct($prefix, $pattern, $recursive)
+ {
+ $this->prefix = realpath($prefix) ?: (file_exists($prefix) ? $prefix : false);
+ $this->pattern = $pattern;
+ $this->recursive = $recursive;
+
+ if (false === $this->prefix) {
+ throw new \InvalidArgumentException(sprintf('The path "%s" does not exist.', $prefix));
+ }
+ }
+
+ public function getPrefix()
+ {
+ return $this->prefix;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __toString()
+ {
+ return 'glob.'.$this->prefix.$this->pattern.(int) $this->recursive;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isFresh($timestamp)
+ {
+ $hash = $this->computeHash();
+
+ if (null === $this->hash) {
+ $this->hash = $hash;
+ }
+
+ return $this->hash === $hash;
+ }
+
+ public function serialize()
+ {
+ if (null === $this->hash) {
+ $this->hash = $this->computeHash();
+ }
+
+ return serialize(array($this->prefix, $this->pattern, $this->recursive, $this->hash));
+ }
+
+ public function unserialize($serialized)
+ {
+ list($this->prefix, $this->pattern, $this->recursive, $this->hash) = unserialize($serialized);
+ }
+
+ public function getIterator()
+ {
+ if (!file_exists($this->prefix) || (!$this->recursive && '' === $this->pattern)) {
+ return;
+ }
+
+ if (false === strpos($this->pattern, '/**/') && (defined('GLOB_BRACE') || false === strpos($this->pattern, '{'))) {
+ foreach (glob($this->prefix.$this->pattern, defined('GLOB_BRACE') ? GLOB_BRACE : 0) as $path) {
+ if ($this->recursive && is_dir($path)) {
+ $files = iterator_to_array(new \RecursiveIteratorIterator(
+ new \RecursiveCallbackFilterIterator(
+ new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
+ function (\SplFileInfo $file) { return '.' !== $file->getBasename()[0]; }
+ ),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ ));
+ uasort($files, function (\SplFileInfo $a, \SplFileInfo $b) {
+ return (string) $a > (string) $b ? 1 : -1;
+ });
+
+ foreach ($files as $path => $info) {
+ if ($info->isFile()) {
+ yield $path => $info;
+ }
+ }
+ } elseif (is_file($path)) {
+ yield $path => new \SplFileInfo($path);
+ }
+ }
+
+ return;
+ }
+
+ if (!class_exists(Finder::class)) {
+ throw new \LogicException(sprintf('Extended glob pattern "%s" cannot be used as the Finder component is not installed.', $this->pattern));
+ }
+
+ $finder = new Finder();
+ $regex = Glob::toRegex($this->pattern);
+ if ($this->recursive) {
+ $regex = substr_replace($regex, '(/|$)', -2, 1);
+ }
+
+ $prefixLen = strlen($this->prefix);
+ foreach ($finder->followLinks()->sortByName()->in($this->prefix) as $path => $info) {
+ if (preg_match($regex, substr($path, $prefixLen)) && $info->isFile()) {
+ yield $path => $info;
+ }
+ }
+ }
+
+ private function computeHash()
+ {
+ $hash = hash_init('md5');
+
+ foreach ($this->getIterator() as $path => $info) {
+ hash_update($hash, $path."\n");
+ }
+
+ return hash_final($hash);
+ }
+}
diff --git a/console/skel/symfony/config/Resource/ReflectionClassResource.php b/console/skel/symfony/config/Resource/ReflectionClassResource.php
new file mode 100644
index 0000000..a8e1f96
--- /dev/null
+++ b/console/skel/symfony/config/Resource/ReflectionClassResource.php
@@ -0,0 +1,181 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+/**
+ * @author Nicolas Grekas
+ */
+class ReflectionClassResource implements SelfCheckingResourceInterface, \Serializable
+{
+ private $files = array();
+ private $className;
+ private $classReflector;
+ private $excludedVendors = array();
+ private $hash;
+
+ public function __construct(\ReflectionClass $classReflector, $excludedVendors = array())
+ {
+ $this->className = $classReflector->name;
+ $this->classReflector = $classReflector;
+ $this->excludedVendors = $excludedVendors;
+ }
+
+ public function isFresh($timestamp)
+ {
+ if (null === $this->hash) {
+ $this->hash = $this->computeHash();
+ $this->loadFiles($this->classReflector);
+ }
+
+ foreach ($this->files as $file => $v) {
+ if (!file_exists($file)) {
+ return false;
+ }
+
+ if (@filemtime($file) > $timestamp) {
+ return $this->hash === $this->computeHash();
+ }
+ }
+
+ return true;
+ }
+
+ public function __toString()
+ {
+ return 'reflection.'.$this->className;
+ }
+
+ public function serialize()
+ {
+ if (null === $this->hash) {
+ $this->hash = $this->computeHash();
+ $this->loadFiles($this->classReflector);
+ }
+
+ return serialize(array($this->files, $this->className, $this->hash));
+ }
+
+ public function unserialize($serialized)
+ {
+ list($this->files, $this->className, $this->hash) = unserialize($serialized);
+ }
+
+ private function loadFiles(\ReflectionClass $class)
+ {
+ foreach ($class->getInterfaces() as $v) {
+ $this->loadFiles($v);
+ }
+ do {
+ $file = $class->getFileName();
+ if (false !== $file && file_exists($file)) {
+ foreach ($this->excludedVendors as $vendor) {
+ if (0 === strpos($file, $vendor) && false !== strpbrk(substr($file, strlen($vendor), 1), '/'.DIRECTORY_SEPARATOR)) {
+ $file = false;
+ break;
+ }
+ }
+ if ($file) {
+ $this->files[$file] = null;
+ }
+ }
+ foreach ($class->getTraits() as $v) {
+ $this->loadFiles($v);
+ }
+ } while ($class = $class->getParentClass());
+ }
+
+ private function computeHash()
+ {
+ if (null === $this->classReflector) {
+ try {
+ $this->classReflector = new \ReflectionClass($this->className);
+ } catch (\ReflectionException $e) {
+ // the class does not exist anymore
+ return false;
+ }
+ }
+ $hash = hash_init('md5');
+
+ foreach ($this->generateSignature($this->classReflector) as $info) {
+ hash_update($hash, $info);
+ }
+
+ return hash_final($hash);
+ }
+
+ private function generateSignature(\ReflectionClass $class)
+ {
+ yield $class->getDocComment().$class->getModifiers();
+
+ if ($class->isTrait()) {
+ yield print_r(class_uses($class->name), true);
+ } else {
+ yield print_r(class_parents($class->name), true);
+ yield print_r(class_implements($class->name), true);
+ yield print_r($class->getConstants(), true);
+ }
+
+ if (!$class->isInterface()) {
+ $defaults = $class->getDefaultProperties();
+
+ foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $p) {
+ yield $p->getDocComment().$p;
+ yield print_r($defaults[$p->name], true);
+ }
+ }
+
+ if (defined('HHVM_VERSION')) {
+ foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) {
+ // workaround HHVM bug with variadics, see https://github.com/facebook/hhvm/issues/5762
+ yield preg_replace('/^ @@.*/m', '', new ReflectionMethodHhvmWrapper($m->class, $m->name));
+ }
+ } else {
+ foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) {
+ yield preg_replace('/^ @@.*/m', '', $m);
+
+ $defaults = array();
+ foreach ($m->getParameters() as $p) {
+ $defaults[$p->name] = $p->isDefaultValueAvailable() ? $p->getDefaultValue() : null;
+ }
+ yield print_r($defaults, true);
+ }
+ }
+ }
+}
+
+/**
+ * @internal
+ */
+class ReflectionMethodHhvmWrapper extends \ReflectionMethod
+{
+ public function getParameters()
+ {
+ $params = array();
+
+ foreach (parent::getParameters() as $i => $p) {
+ $params[] = new ReflectionParameterHhvmWrapper(array($this->class, $this->name), $i);
+ }
+
+ return $params;
+ }
+}
+
+/**
+ * @internal
+ */
+class ReflectionParameterHhvmWrapper extends \ReflectionParameter
+{
+ public function getDefaultValue()
+ {
+ return array($this->isVariadic(), $this->isDefaultValueAvailable() ? parent::getDefaultValue() : null);
+ }
+}
diff --git a/console/skel/symfony/config/Resource/ResourceInterface.php b/console/skel/symfony/config/Resource/ResourceInterface.php
new file mode 100644
index 0000000..d98fd42
--- /dev/null
+++ b/console/skel/symfony/config/Resource/ResourceInterface.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+/**
+ * ResourceInterface is the interface that must be implemented by all Resource classes.
+ *
+ * @author Fabien Potencier
+ */
+interface ResourceInterface
+{
+ /**
+ * Returns a string representation of the Resource.
+ *
+ * This method is necessary to allow for resource de-duplication, for example by means
+ * of array_unique(). The string returned need not have a particular meaning, but has
+ * to be identical for different ResourceInterface instances referring to the same
+ * resource; and it should be unlikely to collide with that of other, unrelated
+ * resource instances.
+ *
+ * @return string A string representation unique to the underlying Resource
+ */
+ public function __toString();
+}
diff --git a/console/skel/symfony/config/Resource/SelfCheckingResourceChecker.php b/console/skel/symfony/config/Resource/SelfCheckingResourceChecker.php
new file mode 100644
index 0000000..d72203b
--- /dev/null
+++ b/console/skel/symfony/config/Resource/SelfCheckingResourceChecker.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+use Symfony\Component\Config\ResourceCheckerInterface;
+
+/**
+ * Resource checker for instances of SelfCheckingResourceInterface.
+ *
+ * As these resources perform the actual check themselves, we can provide
+ * this class as a standard way of validating them.
+ *
+ * @author Matthias Pigulla
+ */
+class SelfCheckingResourceChecker implements ResourceCheckerInterface
+{
+ public function supports(ResourceInterface $metadata)
+ {
+ return $metadata instanceof SelfCheckingResourceInterface;
+ }
+
+ public function isFresh(ResourceInterface $resource, $timestamp)
+ {
+ /* @var SelfCheckingResourceInterface $resource */
+ return $resource->isFresh($timestamp);
+ }
+}
diff --git a/console/skel/symfony/config/Resource/SelfCheckingResourceInterface.php b/console/skel/symfony/config/Resource/SelfCheckingResourceInterface.php
new file mode 100644
index 0000000..c08d969
--- /dev/null
+++ b/console/skel/symfony/config/Resource/SelfCheckingResourceInterface.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+/**
+ * Interface for Resources that can check for freshness autonomously,
+ * without special support from external services.
+ *
+ * @author Matthias Pigulla
+ */
+interface SelfCheckingResourceInterface extends ResourceInterface
+{
+ /**
+ * Returns true if the resource has not been updated since the given timestamp.
+ *
+ * @param int $timestamp The last time the resource was loaded
+ *
+ * @return bool True if the resource has not been updated, false otherwise
+ */
+ public function isFresh(int $timestamp);
+}
diff --git a/console/skel/symfony/config/ResourceCheckerConfigCache.php b/console/skel/symfony/config/ResourceCheckerConfigCache.php
new file mode 100644
index 0000000..c0aef1d
--- /dev/null
+++ b/console/skel/symfony/config/ResourceCheckerConfigCache.php
@@ -0,0 +1,164 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+use Symfony\Component\Config\Resource\ResourceInterface;
+use Symfony\Component\Filesystem\Exception\IOException;
+use Symfony\Component\Filesystem\Filesystem;
+
+/**
+ * ResourceCheckerConfigCache uses instances of ResourceCheckerInterface
+ * to check whether cached data is still fresh.
+ *
+ * @author Matthias Pigulla
+ */
+class ResourceCheckerConfigCache implements ConfigCacheInterface
+{
+ /**
+ * @var string
+ */
+ private $file;
+
+ /**
+ * @var iterable|ResourceCheckerInterface[]
+ */
+ private $resourceCheckers;
+
+ /**
+ * @param string $file The absolute cache path
+ * @param iterable|ResourceCheckerInterface[] $resourceCheckers The ResourceCheckers to use for the freshness check
+ */
+ public function __construct($file, $resourceCheckers = array())
+ {
+ $this->file = $file;
+ $this->resourceCheckers = $resourceCheckers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPath()
+ {
+ return $this->file;
+ }
+
+ /**
+ * Checks if the cache is still fresh.
+ *
+ * This implementation will make a decision solely based on the ResourceCheckers
+ * passed in the constructor.
+ *
+ * The first ResourceChecker that supports a given resource is considered authoritative.
+ * Resources with no matching ResourceChecker will silently be ignored and considered fresh.
+ *
+ * @return bool true if the cache is fresh, false otherwise
+ */
+ public function isFresh()
+ {
+ if (!is_file($this->file)) {
+ return false;
+ }
+
+ if (!$this->resourceCheckers) {
+ return true; // shortcut - if we don't have any checkers we don't need to bother with the meta file at all
+ }
+
+ $metadata = $this->getMetaFile();
+ if (!is_file($metadata)) {
+ return false;
+ }
+
+ $e = null;
+ $meta = false;
+ $time = filemtime($this->file);
+ $signalingException = new \UnexpectedValueException();
+ $prevUnserializeHandler = ini_set('unserialize_callback_func', '');
+ $prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context) use (&$prevErrorHandler, $signalingException) {
+ if (E_WARNING === $type && 'Class __PHP_Incomplete_Class has no unserializer' === $msg) {
+ throw $signalingException;
+ }
+
+ return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : false;
+ });
+
+ try {
+ $meta = unserialize(file_get_contents($metadata));
+ } catch (\Error $e) {
+ } catch (\Exception $e) {
+ }
+ restore_error_handler();
+ ini_set('unserialize_callback_func', $prevUnserializeHandler);
+ if (null !== $e && $e !== $signalingException) {
+ throw $e;
+ }
+ if (false === $meta) {
+ return false;
+ }
+
+ foreach ($meta as $resource) {
+ /* @var ResourceInterface $resource */
+ foreach ($this->resourceCheckers as $checker) {
+ if (!$checker->supports($resource)) {
+ continue; // next checker
+ }
+ if ($checker->isFresh($resource, $time)) {
+ break; // no need to further check this resource
+ }
+
+ return false; // cache is stale
+ }
+ // no suitable checker found, ignore this resource
+ }
+
+ return true;
+ }
+
+ /**
+ * Writes cache.
+ *
+ * @param string $content The content to write in the cache
+ * @param ResourceInterface[] $metadata An array of metadata
+ *
+ * @throws \RuntimeException When cache file can't be written
+ */
+ public function write($content, array $metadata = null)
+ {
+ $mode = 0666;
+ $umask = umask();
+ $filesystem = new Filesystem();
+ $filesystem->dumpFile($this->file, $content, null);
+ try {
+ $filesystem->chmod($this->file, $mode, $umask);
+ } catch (IOException $e) {
+ // discard chmod failure (some filesystem may not support it)
+ }
+
+ if (null !== $metadata) {
+ $filesystem->dumpFile($this->getMetaFile(), serialize($metadata), null);
+ try {
+ $filesystem->chmod($this->getMetaFile(), $mode, $umask);
+ } catch (IOException $e) {
+ // discard chmod failure (some filesystem may not support it)
+ }
+ }
+ }
+
+ /**
+ * Gets the meta file path.
+ *
+ * @return string The meta file path
+ */
+ private function getMetaFile()
+ {
+ return $this->file.'.meta';
+ }
+}
diff --git a/console/skel/symfony/config/ResourceCheckerConfigCacheFactory.php b/console/skel/symfony/config/ResourceCheckerConfigCacheFactory.php
new file mode 100644
index 0000000..ee75efb
--- /dev/null
+++ b/console/skel/symfony/config/ResourceCheckerConfigCacheFactory.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+/**
+ * A ConfigCacheFactory implementation that validates the
+ * cache with an arbitrary set of ResourceCheckers.
+ *
+ * @author Matthias Pigulla
+ */
+class ResourceCheckerConfigCacheFactory implements ConfigCacheFactoryInterface
+{
+ /**
+ * @var iterable|ResourceCheckerInterface[]
+ */
+ private $resourceCheckers = array();
+
+ /**
+ * @param iterable|ResourceCheckerInterface[] $resourceCheckers
+ */
+ public function __construct($resourceCheckers = array())
+ {
+ $this->resourceCheckers = $resourceCheckers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cache($file, $callback)
+ {
+ if (!is_callable($callback)) {
+ throw new \InvalidArgumentException(sprintf('Invalid type for callback argument. Expected callable, but got "%s".', gettype($callback)));
+ }
+
+ $cache = new ResourceCheckerConfigCache($file, $this->resourceCheckers);
+ if (!$cache->isFresh()) {
+ call_user_func($callback, $cache);
+ }
+
+ return $cache;
+ }
+}
diff --git a/console/skel/symfony/config/ResourceCheckerInterface.php b/console/skel/symfony/config/ResourceCheckerInterface.php
new file mode 100644
index 0000000..612d777
--- /dev/null
+++ b/console/skel/symfony/config/ResourceCheckerInterface.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+use Symfony\Component\Config\Resource\ResourceInterface;
+
+/**
+ * Interface for ResourceCheckers.
+ *
+ * When a ResourceCheckerConfigCache instance is checked for freshness, all its associated
+ * metadata resources are passed to ResourceCheckers. The ResourceCheckers
+ * can then inspect the resources and decide whether the cache can be considered
+ * fresh or not.
+ *
+ * @author Matthias Pigulla
+ * @author Benjamin Klotz
+ */
+interface ResourceCheckerInterface
+{
+ /**
+ * Queries the ResourceChecker whether it can validate a given
+ * resource or not.
+ *
+ * @param ResourceInterface $metadata The resource to be checked for freshness
+ *
+ * @return bool True if the ResourceChecker can handle this resource type, false if not
+ */
+ public function supports(ResourceInterface $metadata);
+
+ /**
+ * Validates the resource.
+ *
+ * @param ResourceInterface $resource The resource to be validated
+ * @param int $timestamp The timestamp at which the cache associated with this resource was created
+ *
+ * @return bool True if the resource has not changed since the given timestamp, false otherwise
+ */
+ public function isFresh(ResourceInterface $resource, $timestamp);
+}
diff --git a/console/skel/symfony/config/Tests/ConfigCacheFactoryTest.php b/console/skel/symfony/config/Tests/ConfigCacheFactoryTest.php
new file mode 100644
index 0000000..c523e5c
--- /dev/null
+++ b/console/skel/symfony/config/Tests/ConfigCacheFactoryTest.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\ConfigCacheFactory;
+
+class ConfigCacheFactoryTest extends TestCase
+{
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Invalid type for callback argument. Expected callable, but got "object".
+ */
+ public function testCachWithInvalidCallback()
+ {
+ $cacheFactory = new ConfigCacheFactory(true);
+
+ $cacheFactory->cache('file', new \stdClass());
+ }
+}
diff --git a/console/skel/symfony/config/Tests/ConfigCacheTest.php b/console/skel/symfony/config/Tests/ConfigCacheTest.php
new file mode 100644
index 0000000..bf8131d
--- /dev/null
+++ b/console/skel/symfony/config/Tests/ConfigCacheTest.php
@@ -0,0 +1,99 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\ConfigCache;
+use Symfony\Component\Config\Tests\Resource\ResourceStub;
+
+class ConfigCacheTest extends TestCase
+{
+ private $cacheFile = null;
+
+ protected function setUp()
+ {
+ $this->cacheFile = tempnam(sys_get_temp_dir(), 'config_');
+ }
+
+ protected function tearDown()
+ {
+ $files = array($this->cacheFile, $this->cacheFile.'.meta');
+
+ foreach ($files as $file) {
+ if (file_exists($file)) {
+ unlink($file);
+ }
+ }
+ }
+
+ /**
+ * @dataProvider debugModes
+ */
+ public function testCacheIsNotValidIfNothingHasBeenCached($debug)
+ {
+ unlink($this->cacheFile); // remove tempnam() side effect
+ $cache = new ConfigCache($this->cacheFile, $debug);
+
+ $this->assertFalse($cache->isFresh());
+ }
+
+ public function testIsAlwaysFreshInProduction()
+ {
+ $staleResource = new ResourceStub();
+ $staleResource->setFresh(false);
+
+ $cache = new ConfigCache($this->cacheFile, false);
+ $cache->write('', array($staleResource));
+
+ $this->assertTrue($cache->isFresh());
+ }
+
+ /**
+ * @dataProvider debugModes
+ */
+ public function testIsFreshWhenNoResourceProvided($debug)
+ {
+ $cache = new ConfigCache($this->cacheFile, $debug);
+ $cache->write('', array());
+ $this->assertTrue($cache->isFresh());
+ }
+
+ public function testFreshResourceInDebug()
+ {
+ $freshResource = new ResourceStub();
+ $freshResource->setFresh(true);
+
+ $cache = new ConfigCache($this->cacheFile, true);
+ $cache->write('', array($freshResource));
+
+ $this->assertTrue($cache->isFresh());
+ }
+
+ public function testStaleResourceInDebug()
+ {
+ $staleResource = new ResourceStub();
+ $staleResource->setFresh(false);
+
+ $cache = new ConfigCache($this->cacheFile, true);
+ $cache->write('', array($staleResource));
+
+ $this->assertFalse($cache->isFresh());
+ }
+
+ public function debugModes()
+ {
+ return array(
+ array(true),
+ array(false),
+ );
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/ArrayNodeTest.php b/console/skel/symfony/config/Tests/Definition/ArrayNodeTest.php
new file mode 100644
index 0000000..0b5565e
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/ArrayNodeTest.php
@@ -0,0 +1,219 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\ArrayNode;
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+use Symfony\Component\Config\Definition\ScalarNode;
+
+class ArrayNodeTest extends TestCase
+{
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException
+ */
+ public function testNormalizeThrowsExceptionWhenFalseIsNotAllowed()
+ {
+ $node = new ArrayNode('root');
+ $node->normalize(false);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
+ * @expectedExceptionMessage Unrecognized option "foo" under "root"
+ */
+ public function testExceptionThrownOnUnrecognizedChild()
+ {
+ $node = new ArrayNode('root');
+ $node->normalize(array('foo' => 'bar'));
+ }
+
+ public function ignoreAndRemoveMatrixProvider()
+ {
+ $unrecognizedOptionException = new InvalidConfigurationException('Unrecognized option "foo" under "root"');
+
+ return array(
+ array(true, true, array(), 'no exception is thrown for an unrecognized child if the ignoreExtraKeys option is set to true'),
+ array(true, false, array('foo' => 'bar'), 'extra keys are not removed when ignoreExtraKeys second option is set to false'),
+ array(false, true, $unrecognizedOptionException),
+ array(false, false, $unrecognizedOptionException),
+ );
+ }
+
+ /**
+ * @dataProvider ignoreAndRemoveMatrixProvider
+ */
+ public function testIgnoreAndRemoveBehaviors($ignore, $remove, $expected, $message = '')
+ {
+ if ($expected instanceof \Exception) {
+ if (method_exists($this, 'expectException')) {
+ $this->expectException(get_class($expected));
+ $this->expectExceptionMessage($expected->getMessage());
+ } else {
+ $this->setExpectedException(get_class($expected), $expected->getMessage());
+ }
+ }
+ $node = new ArrayNode('root');
+ $node->setIgnoreExtraKeys($ignore, $remove);
+ $result = $node->normalize(array('foo' => 'bar'));
+ $this->assertSame($expected, $result, $message);
+ }
+
+ /**
+ * @dataProvider getPreNormalizationTests
+ */
+ public function testPreNormalize($denormalized, $normalized)
+ {
+ $node = new ArrayNode('foo');
+
+ $r = new \ReflectionMethod($node, 'preNormalize');
+ $r->setAccessible(true);
+
+ $this->assertSame($normalized, $r->invoke($node, $denormalized));
+ }
+
+ public function getPreNormalizationTests()
+ {
+ return array(
+ array(
+ array('foo-bar' => 'foo'),
+ array('foo_bar' => 'foo'),
+ ),
+ array(
+ array('foo-bar_moo' => 'foo'),
+ array('foo-bar_moo' => 'foo'),
+ ),
+ array(
+ array('anything-with-dash-and-no-underscore' => 'first', 'no_dash' => 'second'),
+ array('anything_with_dash_and_no_underscore' => 'first', 'no_dash' => 'second'),
+ ),
+ array(
+ array('foo-bar' => null, 'foo_bar' => 'foo'),
+ array('foo-bar' => null, 'foo_bar' => 'foo'),
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider getZeroNamedNodeExamplesData
+ */
+ public function testNodeNameCanBeZero($denormalized, $normalized)
+ {
+ $zeroNode = new ArrayNode(0);
+ $zeroNode->addChild(new ScalarNode('name'));
+ $fiveNode = new ArrayNode(5);
+ $fiveNode->addChild(new ScalarNode(0));
+ $fiveNode->addChild(new ScalarNode('new_key'));
+ $rootNode = new ArrayNode('root');
+ $rootNode->addChild($zeroNode);
+ $rootNode->addChild($fiveNode);
+ $rootNode->addChild(new ScalarNode('string_key'));
+ $r = new \ReflectionMethod($rootNode, 'normalizeValue');
+ $r->setAccessible(true);
+
+ $this->assertSame($normalized, $r->invoke($rootNode, $denormalized));
+ }
+
+ public function getZeroNamedNodeExamplesData()
+ {
+ return array(
+ array(
+ array(
+ 0 => array(
+ 'name' => 'something',
+ ),
+ 5 => array(
+ 0 => 'this won\'t work too',
+ 'new_key' => 'some other value',
+ ),
+ 'string_key' => 'just value',
+ ),
+ array(
+ 0 => array(
+ 'name' => 'something',
+ ),
+ 5 => array(
+ 0 => 'this won\'t work too',
+ 'new_key' => 'some other value',
+ ),
+ 'string_key' => 'just value',
+ ),
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider getPreNormalizedNormalizedOrderedData
+ */
+ public function testChildrenOrderIsMaintainedOnNormalizeValue($prenormalized, $normalized)
+ {
+ $scalar1 = new ScalarNode('1');
+ $scalar2 = new ScalarNode('2');
+ $scalar3 = new ScalarNode('3');
+ $node = new ArrayNode('foo');
+ $node->addChild($scalar1);
+ $node->addChild($scalar3);
+ $node->addChild($scalar2);
+
+ $r = new \ReflectionMethod($node, 'normalizeValue');
+ $r->setAccessible(true);
+
+ $this->assertSame($normalized, $r->invoke($node, $prenormalized));
+ }
+
+ public function getPreNormalizedNormalizedOrderedData()
+ {
+ return array(
+ array(
+ array('2' => 'two', '1' => 'one', '3' => 'three'),
+ array('2' => 'two', '1' => 'one', '3' => 'three'),
+ ),
+ );
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Child nodes must be named.
+ */
+ public function testAddChildEmptyName()
+ {
+ $node = new ArrayNode('root');
+
+ $childNode = new ArrayNode('');
+ $node->addChild($childNode);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage A child node named "foo" already exists.
+ */
+ public function testAddChildNameAlreadyExists()
+ {
+ $node = new ArrayNode('root');
+
+ $childNode = new ArrayNode('foo');
+ $node->addChild($childNode);
+
+ $childNodeWithSameName = new ArrayNode('foo');
+ $node->addChild($childNodeWithSameName);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage The node at path "foo" has no default value.
+ */
+ public function testGetDefaultValueWithoutDefaultValue()
+ {
+ $node = new ArrayNode('foo');
+ $node->getDefaultValue();
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/BooleanNodeTest.php b/console/skel/symfony/config/Tests/Definition/BooleanNodeTest.php
new file mode 100644
index 0000000..ab1d316
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/BooleanNodeTest.php
@@ -0,0 +1,74 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\BooleanNode;
+
+class BooleanNodeTest extends TestCase
+{
+ /**
+ * @dataProvider getValidValues
+ */
+ public function testNormalize($value)
+ {
+ $node = new BooleanNode('test');
+ $this->assertSame($value, $node->normalize($value));
+ }
+
+ /**
+ * @dataProvider getValidValues
+ *
+ * @param bool $value
+ */
+ public function testValidNonEmptyValues($value)
+ {
+ $node = new BooleanNode('test');
+ $node->setAllowEmptyValue(false);
+
+ $this->assertSame($value, $node->finalize($value));
+ }
+
+ public function getValidValues()
+ {
+ return array(
+ array(false),
+ array(true),
+ );
+ }
+
+ /**
+ * @dataProvider getInvalidValues
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException
+ */
+ public function testNormalizeThrowsExceptionOnInvalidValues($value)
+ {
+ $node = new BooleanNode('test');
+ $node->normalize($value);
+ }
+
+ public function getInvalidValues()
+ {
+ return array(
+ array(null),
+ array(''),
+ array('foo'),
+ array(0),
+ array(1),
+ array(0.0),
+ array(0.1),
+ array(array()),
+ array(array('foo' => 'bar')),
+ array(new \stdClass()),
+ );
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php b/console/skel/symfony/config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php
new file mode 100644
index 0000000..f2a3235
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php
@@ -0,0 +1,295 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition\Builder;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
+use Symfony\Component\Config\Definition\Processor;
+use Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition;
+use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
+
+class ArrayNodeDefinitionTest extends TestCase
+{
+ public function testAppendingSomeNode()
+ {
+ $parent = new ArrayNodeDefinition('root');
+ $child = new ScalarNodeDefinition('child');
+
+ $parent
+ ->children()
+ ->scalarNode('foo')->end()
+ ->scalarNode('bar')->end()
+ ->end()
+ ->append($child);
+
+ $this->assertCount(3, $this->getField($parent, 'children'));
+ $this->assertTrue(in_array($child, $this->getField($parent, 'children')));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidDefinitionException
+ * @dataProvider providePrototypeNodeSpecificCalls
+ */
+ public function testPrototypeNodeSpecificOption($method, $args)
+ {
+ $node = new ArrayNodeDefinition('root');
+
+ call_user_func_array(array($node, $method), $args);
+
+ $node->getNode();
+ }
+
+ public function providePrototypeNodeSpecificCalls()
+ {
+ return array(
+ array('defaultValue', array(array())),
+ array('addDefaultChildrenIfNoneSet', array()),
+ array('requiresAtLeastOneElement', array()),
+ array('useAttributeAsKey', array('foo')),
+ );
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidDefinitionException
+ */
+ public function testConcreteNodeSpecificOption()
+ {
+ $node = new ArrayNodeDefinition('root');
+ $node
+ ->addDefaultsIfNotSet()
+ ->prototype('array')
+ ;
+ $node->getNode();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidDefinitionException
+ */
+ public function testPrototypeNodesCantHaveADefaultValueWhenUsingDefaultChildren()
+ {
+ $node = new ArrayNodeDefinition('root');
+ $node
+ ->defaultValue(array())
+ ->addDefaultChildrenIfNoneSet('foo')
+ ->prototype('array')
+ ;
+ $node->getNode();
+ }
+
+ public function testPrototypedArrayNodeDefaultWhenUsingDefaultChildren()
+ {
+ $node = new ArrayNodeDefinition('root');
+ $node
+ ->addDefaultChildrenIfNoneSet()
+ ->prototype('array')
+ ;
+ $tree = $node->getNode();
+ $this->assertEquals(array(array()), $tree->getDefaultValue());
+ }
+
+ /**
+ * @dataProvider providePrototypedArrayNodeDefaults
+ */
+ public function testPrototypedArrayNodeDefault($args, $shouldThrowWhenUsingAttrAsKey, $shouldThrowWhenNotUsingAttrAsKey, $defaults)
+ {
+ $node = new ArrayNodeDefinition('root');
+ $node
+ ->addDefaultChildrenIfNoneSet($args)
+ ->prototype('array')
+ ;
+
+ try {
+ $tree = $node->getNode();
+ $this->assertFalse($shouldThrowWhenNotUsingAttrAsKey);
+ $this->assertEquals($defaults, $tree->getDefaultValue());
+ } catch (InvalidDefinitionException $e) {
+ $this->assertTrue($shouldThrowWhenNotUsingAttrAsKey);
+ }
+
+ $node = new ArrayNodeDefinition('root');
+ $node
+ ->useAttributeAsKey('attr')
+ ->addDefaultChildrenIfNoneSet($args)
+ ->prototype('array')
+ ;
+
+ try {
+ $tree = $node->getNode();
+ $this->assertFalse($shouldThrowWhenUsingAttrAsKey);
+ $this->assertEquals($defaults, $tree->getDefaultValue());
+ } catch (InvalidDefinitionException $e) {
+ $this->assertTrue($shouldThrowWhenUsingAttrAsKey);
+ }
+ }
+
+ public function providePrototypedArrayNodeDefaults()
+ {
+ return array(
+ array(null, true, false, array(array())),
+ array(2, true, false, array(array(), array())),
+ array('2', false, true, array('2' => array())),
+ array('foo', false, true, array('foo' => array())),
+ array(array('foo'), false, true, array('foo' => array())),
+ array(array('foo', 'bar'), false, true, array('foo' => array(), 'bar' => array())),
+ );
+ }
+
+ public function testNestedPrototypedArrayNodes()
+ {
+ $nodeDefinition = new ArrayNodeDefinition('root');
+ $nodeDefinition
+ ->addDefaultChildrenIfNoneSet()
+ ->prototype('array')
+ ->prototype('array')
+ ;
+ $node = $nodeDefinition->getNode();
+
+ $this->assertInstanceOf('Symfony\Component\Config\Definition\PrototypedArrayNode', $node);
+ $this->assertInstanceOf('Symfony\Component\Config\Definition\PrototypedArrayNode', $node->getPrototype());
+ }
+
+ public function testEnabledNodeDefaults()
+ {
+ $node = new ArrayNodeDefinition('root');
+ $node
+ ->canBeEnabled()
+ ->children()
+ ->scalarNode('foo')->defaultValue('bar')->end()
+ ;
+
+ $this->assertEquals(array('enabled' => false, 'foo' => 'bar'), $node->getNode()->getDefaultValue());
+ }
+
+ /**
+ * @dataProvider getEnableableNodeFixtures
+ */
+ public function testTrueEnableEnabledNode($expected, $config, $message)
+ {
+ $processor = new Processor();
+ $node = new ArrayNodeDefinition('root');
+ $node
+ ->canBeEnabled()
+ ->children()
+ ->scalarNode('foo')->defaultValue('bar')->end()
+ ;
+
+ $this->assertEquals(
+ $expected,
+ $processor->process($node->getNode(), $config),
+ $message
+ );
+ }
+
+ public function testCanBeDisabled()
+ {
+ $node = new ArrayNodeDefinition('root');
+ $node->canBeDisabled();
+
+ $this->assertTrue($this->getField($node, 'addDefaults'));
+ $this->assertEquals(array('enabled' => false), $this->getField($node, 'falseEquivalent'));
+ $this->assertEquals(array('enabled' => true), $this->getField($node, 'trueEquivalent'));
+ $this->assertEquals(array('enabled' => true), $this->getField($node, 'nullEquivalent'));
+
+ $nodeChildren = $this->getField($node, 'children');
+ $this->assertArrayHasKey('enabled', $nodeChildren);
+
+ $enabledNode = $nodeChildren['enabled'];
+ $this->assertTrue($this->getField($enabledNode, 'default'));
+ $this->assertTrue($this->getField($enabledNode, 'defaultValue'));
+ }
+
+ public function testIgnoreExtraKeys()
+ {
+ $node = new ArrayNodeDefinition('root');
+
+ $this->assertFalse($this->getField($node, 'ignoreExtraKeys'));
+
+ $result = $node->ignoreExtraKeys();
+
+ $this->assertEquals($node, $result);
+ $this->assertTrue($this->getField($node, 'ignoreExtraKeys'));
+ }
+
+ public function testNormalizeKeys()
+ {
+ $node = new ArrayNodeDefinition('root');
+
+ $this->assertTrue($this->getField($node, 'normalizeKeys'));
+
+ $result = $node->normalizeKeys(false);
+
+ $this->assertEquals($node, $result);
+ $this->assertFalse($this->getField($node, 'normalizeKeys'));
+ }
+
+ public function testPrototypeVariable()
+ {
+ $node = new ArrayNodeDefinition('root');
+ $this->assertEquals($node->prototype('variable'), $node->variablePrototype());
+ }
+
+ public function testPrototypeScalar()
+ {
+ $node = new ArrayNodeDefinition('root');
+ $this->assertEquals($node->prototype('scalar'), $node->scalarPrototype());
+ }
+
+ public function testPrototypeBoolean()
+ {
+ $node = new ArrayNodeDefinition('root');
+ $this->assertEquals($node->prototype('boolean'), $node->booleanPrototype());
+ }
+
+ public function testPrototypeInteger()
+ {
+ $node = new ArrayNodeDefinition('root');
+ $this->assertEquals($node->prototype('integer'), $node->integerPrototype());
+ }
+
+ public function testPrototypeFloat()
+ {
+ $node = new ArrayNodeDefinition('root');
+ $this->assertEquals($node->prototype('float'), $node->floatPrototype());
+ }
+
+ public function testPrototypeArray()
+ {
+ $node = new ArrayNodeDefinition('root');
+ $this->assertEquals($node->prototype('array'), $node->arrayPrototype());
+ }
+
+ public function testPrototypeEnum()
+ {
+ $node = new ArrayNodeDefinition('root');
+ $this->assertEquals($node->prototype('enum'), $node->enumPrototype());
+ }
+
+ public function getEnableableNodeFixtures()
+ {
+ return array(
+ array(array('enabled' => true, 'foo' => 'bar'), array(true), 'true enables an enableable node'),
+ array(array('enabled' => true, 'foo' => 'bar'), array(null), 'null enables an enableable node'),
+ array(array('enabled' => true, 'foo' => 'bar'), array(array('enabled' => true)), 'An enableable node can be enabled'),
+ array(array('enabled' => true, 'foo' => 'baz'), array(array('foo' => 'baz')), 'any configuration enables an enableable node'),
+ array(array('enabled' => false, 'foo' => 'baz'), array(array('foo' => 'baz', 'enabled' => false)), 'An enableable node can be disabled'),
+ array(array('enabled' => false, 'foo' => 'bar'), array(false), 'false disables an enableable node'),
+ );
+ }
+
+ protected function getField($object, $field)
+ {
+ $reflection = new \ReflectionProperty($object, $field);
+ $reflection->setAccessible(true);
+
+ return $reflection->getValue($object);
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/Builder/BooleanNodeDefinitionTest.php b/console/skel/symfony/config/Tests/Definition/Builder/BooleanNodeDefinitionTest.php
new file mode 100644
index 0000000..291dd60
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/Builder/BooleanNodeDefinitionTest.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition\Builder;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition;
+
+class BooleanNodeDefinitionTest extends TestCase
+{
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidDefinitionException
+ * @expectedExceptionMessage ->cannotBeEmpty() is not applicable to BooleanNodeDefinition.
+ */
+ public function testCannotBeEmptyThrowsAnException()
+ {
+ $def = new BooleanNodeDefinition('foo');
+ $def->cannotBeEmpty();
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/Builder/EnumNodeDefinitionTest.php b/console/skel/symfony/config/Tests/Definition/Builder/EnumNodeDefinitionTest.php
new file mode 100644
index 0000000..0f09b78
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/Builder/EnumNodeDefinitionTest.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition\Builder;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\Builder\EnumNodeDefinition;
+
+class EnumNodeDefinitionTest extends TestCase
+{
+ public function testWithOneValue()
+ {
+ $def = new EnumNodeDefinition('foo');
+ $def->values(array('foo'));
+
+ $node = $def->getNode();
+ $this->assertEquals(array('foo'), $node->getValues());
+ }
+
+ public function testWithOneDistinctValue()
+ {
+ $def = new EnumNodeDefinition('foo');
+ $def->values(array('foo', 'foo'));
+
+ $node = $def->getNode();
+ $this->assertEquals(array('foo'), $node->getValues());
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage You must call ->values() on enum nodes.
+ */
+ public function testNoValuesPassed()
+ {
+ $def = new EnumNodeDefinition('foo');
+ $def->getNode();
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage ->values() must be called with at least one value.
+ */
+ public function testWithNoValues()
+ {
+ $def = new EnumNodeDefinition('foo');
+ $def->values(array());
+ }
+
+ public function testGetNode()
+ {
+ $def = new EnumNodeDefinition('foo');
+ $def->values(array('foo', 'bar'));
+
+ $node = $def->getNode();
+ $this->assertEquals(array('foo', 'bar'), $node->getValues());
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/Builder/ExprBuilderTest.php b/console/skel/symfony/config/Tests/Definition/Builder/ExprBuilderTest.php
new file mode 100644
index 0000000..99a1041
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/Builder/ExprBuilderTest.php
@@ -0,0 +1,270 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition\Builder;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+
+class ExprBuilderTest extends TestCase
+{
+ public function testAlwaysExpression()
+ {
+ $test = $this->getTestBuilder()
+ ->always($this->returnClosure('new_value'))
+ ->end();
+
+ $this->assertFinalizedValueIs('new_value', $test);
+ }
+
+ public function testIfTrueExpression()
+ {
+ $test = $this->getTestBuilder()
+ ->ifTrue()
+ ->then($this->returnClosure('new_value'))
+ ->end();
+ $this->assertFinalizedValueIs('new_value', $test, array('key' => true));
+
+ $test = $this->getTestBuilder()
+ ->ifTrue(function ($v) { return true; })
+ ->then($this->returnClosure('new_value'))
+ ->end();
+ $this->assertFinalizedValueIs('new_value', $test);
+
+ $test = $this->getTestBuilder()
+ ->ifTrue(function ($v) { return false; })
+ ->then($this->returnClosure('new_value'))
+ ->end();
+ $this->assertFinalizedValueIs('value', $test);
+ }
+
+ public function testIfStringExpression()
+ {
+ $test = $this->getTestBuilder()
+ ->ifString()
+ ->then($this->returnClosure('new_value'))
+ ->end();
+ $this->assertFinalizedValueIs('new_value', $test);
+
+ $test = $this->getTestBuilder()
+ ->ifString()
+ ->then($this->returnClosure('new_value'))
+ ->end();
+ $this->assertFinalizedValueIs(45, $test, array('key' => 45));
+ }
+
+ public function testIfNullExpression()
+ {
+ $test = $this->getTestBuilder()
+ ->ifNull()
+ ->then($this->returnClosure('new_value'))
+ ->end();
+ $this->assertFinalizedValueIs('new_value', $test, array('key' => null));
+
+ $test = $this->getTestBuilder()
+ ->ifNull()
+ ->then($this->returnClosure('new_value'))
+ ->end();
+ $this->assertFinalizedValueIs('value', $test);
+ }
+
+ public function testIfEmptyExpression()
+ {
+ $test = $this->getTestBuilder()
+ ->ifEmpty()
+ ->then($this->returnClosure('new_value'))
+ ->end();
+ $this->assertFinalizedValueIs('new_value', $test, array('key' => array()));
+
+ $test = $this->getTestBuilder()
+ ->ifEmpty()
+ ->then($this->returnClosure('new_value'))
+ ->end();
+ $this->assertFinalizedValueIs('value', $test);
+ }
+
+ public function testIfArrayExpression()
+ {
+ $test = $this->getTestBuilder()
+ ->ifArray()
+ ->then($this->returnClosure('new_value'))
+ ->end();
+ $this->assertFinalizedValueIs('new_value', $test, array('key' => array()));
+
+ $test = $this->getTestBuilder()
+ ->ifArray()
+ ->then($this->returnClosure('new_value'))
+ ->end();
+ $this->assertFinalizedValueIs('value', $test);
+ }
+
+ public function testIfInArrayExpression()
+ {
+ $test = $this->getTestBuilder()
+ ->ifInArray(array('foo', 'bar', 'value'))
+ ->then($this->returnClosure('new_value'))
+ ->end();
+ $this->assertFinalizedValueIs('new_value', $test);
+
+ $test = $this->getTestBuilder()
+ ->ifInArray(array('foo', 'bar'))
+ ->then($this->returnClosure('new_value'))
+ ->end();
+ $this->assertFinalizedValueIs('value', $test);
+ }
+
+ public function testIfNotInArrayExpression()
+ {
+ $test = $this->getTestBuilder()
+ ->ifNotInArray(array('foo', 'bar'))
+ ->then($this->returnClosure('new_value'))
+ ->end();
+ $this->assertFinalizedValueIs('new_value', $test);
+
+ $test = $this->getTestBuilder()
+ ->ifNotInArray(array('foo', 'bar', 'value_from_config'))
+ ->then($this->returnClosure('new_value'))
+ ->end();
+ $this->assertFinalizedValueIs('new_value', $test);
+ }
+
+ public function testThenEmptyArrayExpression()
+ {
+ $test = $this->getTestBuilder()
+ ->ifString()
+ ->thenEmptyArray()
+ ->end();
+ $this->assertFinalizedValueIs(array(), $test);
+ }
+
+ /**
+ * @dataProvider castToArrayValues
+ */
+ public function testcastToArrayExpression($configValue, $expectedValue)
+ {
+ $test = $this->getTestBuilder()
+ ->castToArray()
+ ->end();
+ $this->assertFinalizedValueIs($expectedValue, $test, array('key' => $configValue));
+ }
+
+ public function castToArrayValues()
+ {
+ yield array('value', array('value'));
+ yield array(-3.14, array(-3.14));
+ yield array(null, array(null));
+ yield array(array('value'), array('value'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
+ */
+ public function testThenInvalid()
+ {
+ $test = $this->getTestBuilder()
+ ->ifString()
+ ->thenInvalid('Invalid value')
+ ->end();
+ $this->finalizeTestBuilder($test);
+ }
+
+ public function testThenUnsetExpression()
+ {
+ $test = $this->getTestBuilder()
+ ->ifString()
+ ->thenUnset()
+ ->end();
+ $this->assertEquals(array(), $this->finalizeTestBuilder($test));
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage You must specify an if part.
+ */
+ public function testEndIfPartNotSpecified()
+ {
+ $this->getTestBuilder()->end();
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage You must specify a then part.
+ */
+ public function testEndThenPartNotSpecified()
+ {
+ $builder = $this->getTestBuilder();
+ $builder->ifPart = 'test';
+ $builder->end();
+ }
+
+ /**
+ * Create a test treebuilder with a variable node, and init the validation.
+ *
+ * @return TreeBuilder
+ */
+ protected function getTestBuilder()
+ {
+ $builder = new TreeBuilder();
+
+ return $builder
+ ->root('test')
+ ->children()
+ ->variableNode('key')
+ ->validate()
+ ;
+ }
+
+ /**
+ * Close the validation process and finalize with the given config.
+ *
+ * @param TreeBuilder $testBuilder The tree builder to finalize
+ * @param array $config The config you want to use for the finalization, if nothing provided
+ * a simple array('key'=>'value') will be used
+ *
+ * @return array The finalized config values
+ */
+ protected function finalizeTestBuilder($testBuilder, $config = null)
+ {
+ return $testBuilder
+ ->end()
+ ->end()
+ ->end()
+ ->buildTree()
+ ->finalize(null === $config ? array('key' => 'value') : $config)
+ ;
+ }
+
+ /**
+ * Return a closure that will return the given value.
+ *
+ * @param mixed $val The value that the closure must return
+ *
+ * @return \Closure
+ */
+ protected function returnClosure($val)
+ {
+ return function ($v) use ($val) {
+ return $val;
+ };
+ }
+
+ /**
+ * Assert that the given test builder, will return the given value.
+ *
+ * @param mixed $value The value to test
+ * @param TreeBuilder $treeBuilder The tree builder to finalize
+ * @param mixed $config The config values that new to be finalized
+ */
+ protected function assertFinalizedValueIs($value, $treeBuilder, $config = null)
+ {
+ $this->assertEquals(array('key' => $value), $this->finalizeTestBuilder($treeBuilder, $config));
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/Builder/NodeBuilderTest.php b/console/skel/symfony/config/Tests/Definition/Builder/NodeBuilderTest.php
new file mode 100644
index 0000000..8877565
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/Builder/NodeBuilderTest.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition\Builder;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\Builder\NodeBuilder as BaseNodeBuilder;
+use Symfony\Component\Config\Definition\Builder\VariableNodeDefinition as BaseVariableNodeDefinition;
+
+class NodeBuilderTest extends TestCase
+{
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testThrowsAnExceptionWhenTryingToCreateANonRegisteredNodeType()
+ {
+ $builder = new BaseNodeBuilder();
+ $builder->node('', 'foobar');
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testThrowsAnExceptionWhenTheNodeClassIsNotFound()
+ {
+ $builder = new BaseNodeBuilder();
+ $builder
+ ->setNodeClass('noclasstype', '\\foo\\bar\\noclass')
+ ->node('', 'noclasstype');
+ }
+
+ public function testAddingANewNodeType()
+ {
+ $class = __NAMESPACE__.'\\SomeNodeDefinition';
+
+ $builder = new BaseNodeBuilder();
+ $node = $builder
+ ->setNodeClass('newtype', $class)
+ ->node('', 'newtype');
+
+ $this->assertInstanceOf($class, $node);
+ }
+
+ public function testOverridingAnExistingNodeType()
+ {
+ $class = __NAMESPACE__.'\\SomeNodeDefinition';
+
+ $builder = new BaseNodeBuilder();
+ $node = $builder
+ ->setNodeClass('variable', $class)
+ ->node('', 'variable');
+
+ $this->assertInstanceOf($class, $node);
+ }
+
+ public function testNodeTypesAreNotCaseSensitive()
+ {
+ $builder = new BaseNodeBuilder();
+
+ $node1 = $builder->node('', 'VaRiAbLe');
+ $node2 = $builder->node('', 'variable');
+
+ $this->assertInstanceOf(get_class($node1), $node2);
+
+ $builder->setNodeClass('CuStOm', __NAMESPACE__.'\\SomeNodeDefinition');
+
+ $node1 = $builder->node('', 'CUSTOM');
+ $node2 = $builder->node('', 'custom');
+
+ $this->assertInstanceOf(get_class($node1), $node2);
+ }
+
+ public function testNumericNodeCreation()
+ {
+ $builder = new BaseNodeBuilder();
+
+ $node = $builder->integerNode('foo')->min(3)->max(5);
+ $this->assertInstanceOf('Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition', $node);
+
+ $node = $builder->floatNode('bar')->min(3.0)->max(5.0);
+ $this->assertInstanceOf('Symfony\Component\Config\Definition\Builder\FloatNodeDefinition', $node);
+ }
+}
+
+class SomeNodeDefinition extends BaseVariableNodeDefinition
+{
+}
diff --git a/console/skel/symfony/config/Tests/Definition/Builder/NumericNodeDefinitionTest.php b/console/skel/symfony/config/Tests/Definition/Builder/NumericNodeDefinitionTest.php
new file mode 100644
index 0000000..5a86e1e
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/Builder/NumericNodeDefinitionTest.php
@@ -0,0 +1,104 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition\Builder;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition as NumericNodeDefinition;
+use Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition;
+use Symfony\Component\Config\Definition\Builder\FloatNodeDefinition;
+
+class NumericNodeDefinitionTest extends TestCase
+{
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage You cannot define a min(4) as you already have a max(3)
+ */
+ public function testIncoherentMinAssertion()
+ {
+ $def = new NumericNodeDefinition('foo');
+ $def->max(3)->min(4);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage You cannot define a max(2) as you already have a min(3)
+ */
+ public function testIncoherentMaxAssertion()
+ {
+ $node = new NumericNodeDefinition('foo');
+ $node->min(3)->max(2);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
+ * @expectedExceptionMessage The value 4 is too small for path "foo". Should be greater than or equal to 5
+ */
+ public function testIntegerMinAssertion()
+ {
+ $def = new IntegerNodeDefinition('foo');
+ $def->min(5)->getNode()->finalize(4);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
+ * @expectedExceptionMessage The value 4 is too big for path "foo". Should be less than or equal to 3
+ */
+ public function testIntegerMaxAssertion()
+ {
+ $def = new IntegerNodeDefinition('foo');
+ $def->max(3)->getNode()->finalize(4);
+ }
+
+ public function testIntegerValidMinMaxAssertion()
+ {
+ $def = new IntegerNodeDefinition('foo');
+ $node = $def->min(3)->max(7)->getNode();
+ $this->assertEquals(4, $node->finalize(4));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
+ * @expectedExceptionMessage The value 400 is too small for path "foo". Should be greater than or equal to 500
+ */
+ public function testFloatMinAssertion()
+ {
+ $def = new FloatNodeDefinition('foo');
+ $def->min(5E2)->getNode()->finalize(4e2);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
+ * @expectedExceptionMessage The value 4.3 is too big for path "foo". Should be less than or equal to 0.3
+ */
+ public function testFloatMaxAssertion()
+ {
+ $def = new FloatNodeDefinition('foo');
+ $def->max(0.3)->getNode()->finalize(4.3);
+ }
+
+ public function testFloatValidMinMaxAssertion()
+ {
+ $def = new FloatNodeDefinition('foo');
+ $node = $def->min(3.0)->max(7e2)->getNode();
+ $this->assertEquals(4.5, $node->finalize(4.5));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidDefinitionException
+ * @expectedExceptionMessage ->cannotBeEmpty() is not applicable to NumericNodeDefinition.
+ */
+ public function testCannotBeEmptyThrowsAnException()
+ {
+ $def = new NumericNodeDefinition('foo');
+ $def->cannotBeEmpty();
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/Builder/TreeBuilderTest.php b/console/skel/symfony/config/Tests/Definition/Builder/TreeBuilderTest.php
new file mode 100644
index 0000000..13304fa
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/Builder/TreeBuilderTest.php
@@ -0,0 +1,138 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition\Builder;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Tests\Definition\Builder\NodeBuilder as CustomNodeBuilder;
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+
+require __DIR__.'/../../Fixtures/Builder/NodeBuilder.php';
+require __DIR__.'/../../Fixtures/Builder/BarNodeDefinition.php';
+require __DIR__.'/../../Fixtures/Builder/VariableNodeDefinition.php';
+
+class TreeBuilderTest extends TestCase
+{
+ public function testUsingACustomNodeBuilder()
+ {
+ $builder = new TreeBuilder();
+ $root = $builder->root('custom', 'array', new CustomNodeBuilder());
+
+ $nodeBuilder = $root->children();
+
+ $this->assertInstanceOf('Symfony\Component\Config\Tests\Definition\Builder\NodeBuilder', $nodeBuilder);
+
+ $nodeBuilder = $nodeBuilder->arrayNode('deeper')->children();
+
+ $this->assertInstanceOf('Symfony\Component\Config\Tests\Definition\Builder\NodeBuilder', $nodeBuilder);
+ }
+
+ public function testOverrideABuiltInNodeType()
+ {
+ $builder = new TreeBuilder();
+ $root = $builder->root('override', 'array', new CustomNodeBuilder());
+
+ $definition = $root->children()->variableNode('variable');
+
+ $this->assertInstanceOf('Symfony\Component\Config\Tests\Definition\Builder\VariableNodeDefinition', $definition);
+ }
+
+ public function testAddANodeType()
+ {
+ $builder = new TreeBuilder();
+ $root = $builder->root('override', 'array', new CustomNodeBuilder());
+
+ $definition = $root->children()->barNode('variable');
+
+ $this->assertInstanceOf('Symfony\Component\Config\Tests\Definition\Builder\BarNodeDefinition', $definition);
+ }
+
+ public function testCreateABuiltInNodeTypeWithACustomNodeBuilder()
+ {
+ $builder = new TreeBuilder();
+ $root = $builder->root('builtin', 'array', new CustomNodeBuilder());
+
+ $definition = $root->children()->booleanNode('boolean');
+
+ $this->assertInstanceOf('Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition', $definition);
+ }
+
+ public function testPrototypedArrayNodeUseTheCustomNodeBuilder()
+ {
+ $builder = new TreeBuilder();
+ $root = $builder->root('override', 'array', new CustomNodeBuilder());
+
+ $root->prototype('bar')->end();
+
+ $this->assertInstanceOf('Symfony\Component\Config\Tests\Fixtures\BarNode', $root->getNode(true)->getPrototype());
+ }
+
+ public function testAnExtendedNodeBuilderGetsPropagatedToTheChildren()
+ {
+ $builder = new TreeBuilder();
+
+ $builder->root('propagation')
+ ->children()
+ ->setNodeClass('extended', 'Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition')
+ ->node('foo', 'extended')->end()
+ ->arrayNode('child')
+ ->children()
+ ->node('foo', 'extended')
+ ->end()
+ ->end()
+ ->end()
+ ->end();
+
+ $node = $builder->buildTree();
+ $children = $node->getChildren();
+
+ $this->assertInstanceOf('Symfony\Component\Config\Definition\BooleanNode', $children['foo']);
+
+ $childChildren = $children['child']->getChildren();
+
+ $this->assertInstanceOf('Symfony\Component\Config\Definition\BooleanNode', $childChildren['foo']);
+ }
+
+ public function testDefinitionInfoGetsTransferredToNode()
+ {
+ $builder = new TreeBuilder();
+
+ $builder->root('test')->info('root info')
+ ->children()
+ ->node('child', 'variable')->info('child info')->defaultValue('default')
+ ->end()
+ ->end();
+
+ $tree = $builder->buildTree();
+ $children = $tree->getChildren();
+
+ $this->assertEquals('root info', $tree->getInfo());
+ $this->assertEquals('child info', $children['child']->getInfo());
+ }
+
+ public function testDefinitionExampleGetsTransferredToNode()
+ {
+ $builder = new TreeBuilder();
+
+ $builder->root('test')
+ ->example(array('key' => 'value'))
+ ->children()
+ ->node('child', 'variable')->info('child info')->defaultValue('default')->example('example')
+ ->end()
+ ->end();
+
+ $tree = $builder->buildTree();
+ $children = $tree->getChildren();
+
+ $this->assertInternalType('array', $tree->getExample());
+ $this->assertEquals('example', $children['child']->getExample());
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/Dumper/XmlReferenceDumperTest.php b/console/skel/symfony/config/Tests/Definition/Dumper/XmlReferenceDumperTest.php
new file mode 100644
index 0000000..fd5f9c8
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/Dumper/XmlReferenceDumperTest.php
@@ -0,0 +1,110 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\Dumper\XmlReferenceDumper;
+use Symfony\Component\Config\Tests\Fixtures\Configuration\ExampleConfiguration;
+
+class XmlReferenceDumperTest extends TestCase
+{
+ public function testDumper()
+ {
+ $configuration = new ExampleConfiguration();
+
+ $dumper = new XmlReferenceDumper();
+ $this->assertEquals($this->getConfigurationAsString(), $dumper->dump($configuration));
+ }
+
+ public function testNamespaceDumper()
+ {
+ $configuration = new ExampleConfiguration();
+
+ $dumper = new XmlReferenceDumper();
+ $this->assertEquals(str_replace('http://example.org/schema/dic/acme_root', 'http://symfony.com/schema/dic/symfony', $this->getConfigurationAsString()), $dumper->dump($configuration, 'http://symfony.com/schema/dic/symfony'));
+ }
+
+ private function getConfigurationAsString()
+ {
+ return str_replace("\n", PHP_EOL, <<<'EOL'
+
+
+
+
+
+
+
+
+
+
+
+ scalar value
+
+
+ scalar value
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+EOL
+ );
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/Dumper/YamlReferenceDumperTest.php b/console/skel/symfony/config/Tests/Definition/Dumper/YamlReferenceDumperTest.php
new file mode 100644
index 0000000..ea5687d
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/Dumper/YamlReferenceDumperTest.php
@@ -0,0 +1,141 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper;
+use Symfony\Component\Config\Tests\Fixtures\Configuration\ExampleConfiguration;
+
+class YamlReferenceDumperTest extends TestCase
+{
+ public function testDumper()
+ {
+ $configuration = new ExampleConfiguration();
+
+ $dumper = new YamlReferenceDumper();
+
+ $this->assertEquals($this->getConfigurationAsString(), $dumper->dump($configuration));
+ }
+
+ public function provideDumpAtPath()
+ {
+ return array(
+ 'Regular node' => array('scalar_true', << array('array', << array('array.child2', << array('cms_pages.page', << array('cms_pages.page.locale', <<assertSame(trim($expected), trim($dumper->dumpAtPath($configuration, $path)));
+ }
+
+ private function getConfigurationAsString()
+ {
+ return <<<'EOL'
+acme_root:
+ boolean: true
+ scalar_empty: ~
+ scalar_null: null
+ scalar_true: true
+ scalar_false: false
+ scalar_default: default
+ scalar_array_empty: []
+ scalar_array_defaults:
+
+ # Defaults:
+ - elem1
+ - elem2
+ scalar_required: ~ # Required
+ node_with_a_looong_name: ~
+ enum_with_default: this # One of "this"; "that"
+ enum: ~ # One of "this"; "that"
+
+ # some info
+ array:
+ child1: ~
+ child2: ~
+
+ # this is a long
+ # multi-line info text
+ # which should be indented
+ child3: ~ # Example: example setting
+ scalar_prototyped: []
+ parameters:
+
+ # Prototype: Parameter name
+ name: ~
+ connections:
+
+ # Prototype
+ -
+ user: ~
+ pass: ~
+ cms_pages:
+
+ # Prototype
+ page:
+
+ # Prototype
+ locale:
+ title: ~ # Required
+ path: ~ # Required
+ pipou:
+
+ # Prototype
+ name: []
+
+EOL;
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/EnumNodeTest.php b/console/skel/symfony/config/Tests/Definition/EnumNodeTest.php
new file mode 100644
index 0000000..e3e7ef0
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/EnumNodeTest.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\EnumNode;
+
+class EnumNodeTest extends TestCase
+{
+ public function testFinalizeValue()
+ {
+ $node = new EnumNode('foo', null, array('foo', 'bar'));
+ $this->assertSame('foo', $node->finalize('foo'));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage $values must contain at least one element.
+ */
+ public function testConstructionWithNoValues()
+ {
+ new EnumNode('foo', null, array());
+ }
+
+ public function testConstructionWithOneValue()
+ {
+ $node = new EnumNode('foo', null, array('foo'));
+ $this->assertSame('foo', $node->finalize('foo'));
+ }
+
+ public function testConstructionWithOneDistinctValue()
+ {
+ $node = new EnumNode('foo', null, array('foo', 'foo'));
+ $this->assertSame('foo', $node->finalize('foo'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
+ * @expectedExceptionMessage The value "foobar" is not allowed for path "foo". Permissible values: "foo", "bar"
+ */
+ public function testFinalizeWithInvalidValue()
+ {
+ $node = new EnumNode('foo', null, array('foo', 'bar'));
+ $node->finalize('foobar');
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/FinalizationTest.php b/console/skel/symfony/config/Tests/Definition/FinalizationTest.php
new file mode 100644
index 0000000..733f600
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/FinalizationTest.php
@@ -0,0 +1,74 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+use Symfony\Component\Config\Definition\Processor;
+use Symfony\Component\Config\Definition\NodeInterface;
+
+class FinalizationTest extends TestCase
+{
+ public function testUnsetKeyWithDeepHierarchy()
+ {
+ $tb = new TreeBuilder();
+ $tree = $tb
+ ->root('config', 'array')
+ ->children()
+ ->node('level1', 'array')
+ ->canBeUnset()
+ ->children()
+ ->node('level2', 'array')
+ ->canBeUnset()
+ ->children()
+ ->node('somevalue', 'scalar')->end()
+ ->node('anothervalue', 'scalar')->end()
+ ->end()
+ ->end()
+ ->node('level1_scalar', 'scalar')->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->buildTree()
+ ;
+
+ $a = array(
+ 'level1' => array(
+ 'level2' => array(
+ 'somevalue' => 'foo',
+ 'anothervalue' => 'bar',
+ ),
+ 'level1_scalar' => 'foo',
+ ),
+ );
+
+ $b = array(
+ 'level1' => array(
+ 'level2' => false,
+ ),
+ );
+
+ $this->assertEquals(array(
+ 'level1' => array(
+ 'level1_scalar' => 'foo',
+ ),
+ ), $this->process($tree, array($a, $b)));
+ }
+
+ protected function process(NodeInterface $tree, array $configs)
+ {
+ $processor = new Processor();
+
+ return $processor->process($tree, $configs);
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/FloatNodeTest.php b/console/skel/symfony/config/Tests/Definition/FloatNodeTest.php
new file mode 100644
index 0000000..b7ec12f
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/FloatNodeTest.php
@@ -0,0 +1,78 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\FloatNode;
+
+class FloatNodeTest extends TestCase
+{
+ /**
+ * @dataProvider getValidValues
+ */
+ public function testNormalize($value)
+ {
+ $node = new FloatNode('test');
+ $this->assertSame($value, $node->normalize($value));
+ }
+
+ /**
+ * @dataProvider getValidValues
+ *
+ * @param int $value
+ */
+ public function testValidNonEmptyValues($value)
+ {
+ $node = new FloatNode('test');
+ $node->setAllowEmptyValue(false);
+
+ $this->assertSame($value, $node->finalize($value));
+ }
+
+ public function getValidValues()
+ {
+ return array(
+ array(1798.0),
+ array(-678.987),
+ array(12.56E45),
+ array(0.0),
+ // Integer are accepted too, they will be cast
+ array(17),
+ array(-10),
+ array(0),
+ );
+ }
+
+ /**
+ * @dataProvider getInvalidValues
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException
+ */
+ public function testNormalizeThrowsExceptionOnInvalidValues($value)
+ {
+ $node = new FloatNode('test');
+ $node->normalize($value);
+ }
+
+ public function getInvalidValues()
+ {
+ return array(
+ array(null),
+ array(''),
+ array('foo'),
+ array(true),
+ array(false),
+ array(array()),
+ array(array('foo' => 'bar')),
+ array(new \stdClass()),
+ );
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/IntegerNodeTest.php b/console/skel/symfony/config/Tests/Definition/IntegerNodeTest.php
new file mode 100644
index 0000000..55e8a13
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/IntegerNodeTest.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\IntegerNode;
+
+class IntegerNodeTest extends TestCase
+{
+ /**
+ * @dataProvider getValidValues
+ */
+ public function testNormalize($value)
+ {
+ $node = new IntegerNode('test');
+ $this->assertSame($value, $node->normalize($value));
+ }
+
+ /**
+ * @dataProvider getValidValues
+ *
+ * @param int $value
+ */
+ public function testValidNonEmptyValues($value)
+ {
+ $node = new IntegerNode('test');
+ $node->setAllowEmptyValue(false);
+
+ $this->assertSame($value, $node->finalize($value));
+ }
+
+ public function getValidValues()
+ {
+ return array(
+ array(1798),
+ array(-678),
+ array(0),
+ );
+ }
+
+ /**
+ * @dataProvider getInvalidValues
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException
+ */
+ public function testNormalizeThrowsExceptionOnInvalidValues($value)
+ {
+ $node = new IntegerNode('test');
+ $node->normalize($value);
+ }
+
+ public function getInvalidValues()
+ {
+ return array(
+ array(null),
+ array(''),
+ array('foo'),
+ array(true),
+ array(false),
+ array(0.0),
+ array(0.1),
+ array(array()),
+ array(array('foo' => 'bar')),
+ array(new \stdClass()),
+ );
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/MergeTest.php b/console/skel/symfony/config/Tests/Definition/MergeTest.php
new file mode 100644
index 0000000..e539e25
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/MergeTest.php
@@ -0,0 +1,196 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+
+class MergeTest extends TestCase
+{
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException
+ */
+ public function testForbiddenOverwrite()
+ {
+ $tb = new TreeBuilder();
+ $tree = $tb
+ ->root('root', 'array')
+ ->children()
+ ->node('foo', 'scalar')
+ ->cannotBeOverwritten()
+ ->end()
+ ->end()
+ ->end()
+ ->buildTree()
+ ;
+
+ $a = array(
+ 'foo' => 'bar',
+ );
+
+ $b = array(
+ 'foo' => 'moo',
+ );
+
+ $tree->merge($a, $b);
+ }
+
+ public function testUnsetKey()
+ {
+ $tb = new TreeBuilder();
+ $tree = $tb
+ ->root('root', 'array')
+ ->children()
+ ->node('foo', 'scalar')->end()
+ ->node('bar', 'scalar')->end()
+ ->node('unsettable', 'array')
+ ->canBeUnset()
+ ->children()
+ ->node('foo', 'scalar')->end()
+ ->node('bar', 'scalar')->end()
+ ->end()
+ ->end()
+ ->node('unsetted', 'array')
+ ->canBeUnset()
+ ->prototype('scalar')->end()
+ ->end()
+ ->end()
+ ->end()
+ ->buildTree()
+ ;
+
+ $a = array(
+ 'foo' => 'bar',
+ 'unsettable' => array(
+ 'foo' => 'a',
+ 'bar' => 'b',
+ ),
+ 'unsetted' => false,
+ );
+
+ $b = array(
+ 'foo' => 'moo',
+ 'bar' => 'b',
+ 'unsettable' => false,
+ 'unsetted' => array('a', 'b'),
+ );
+
+ $this->assertEquals(array(
+ 'foo' => 'moo',
+ 'bar' => 'b',
+ 'unsettable' => false,
+ 'unsetted' => array('a', 'b'),
+ ), $tree->merge($a, $b));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
+ */
+ public function testDoesNotAllowNewKeysInSubsequentConfigs()
+ {
+ $tb = new TreeBuilder();
+ $tree = $tb
+ ->root('config', 'array')
+ ->children()
+ ->node('test', 'array')
+ ->disallowNewKeysInSubsequentConfigs()
+ ->useAttributeAsKey('key')
+ ->prototype('array')
+ ->children()
+ ->node('value', 'scalar')->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->buildTree();
+
+ $a = array(
+ 'test' => array(
+ 'a' => array('value' => 'foo'),
+ ),
+ );
+
+ $b = array(
+ 'test' => array(
+ 'b' => array('value' => 'foo'),
+ ),
+ );
+
+ $tree->merge($a, $b);
+ }
+
+ public function testPerformsNoDeepMerging()
+ {
+ $tb = new TreeBuilder();
+
+ $tree = $tb
+ ->root('config', 'array')
+ ->children()
+ ->node('no_deep_merging', 'array')
+ ->performNoDeepMerging()
+ ->children()
+ ->node('foo', 'scalar')->end()
+ ->node('bar', 'scalar')->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->buildTree()
+ ;
+
+ $a = array(
+ 'no_deep_merging' => array(
+ 'foo' => 'a',
+ 'bar' => 'b',
+ ),
+ );
+
+ $b = array(
+ 'no_deep_merging' => array(
+ 'c' => 'd',
+ ),
+ );
+
+ $this->assertEquals(array(
+ 'no_deep_merging' => array(
+ 'c' => 'd',
+ ),
+ ), $tree->merge($a, $b));
+ }
+
+ public function testPrototypeWithoutAKeyAttribute()
+ {
+ $tb = new TreeBuilder();
+
+ $tree = $tb
+ ->root('config', 'array')
+ ->children()
+ ->arrayNode('append_elements')
+ ->prototype('scalar')->end()
+ ->end()
+ ->end()
+ ->end()
+ ->buildTree()
+ ;
+
+ $a = array(
+ 'append_elements' => array('a', 'b'),
+ );
+
+ $b = array(
+ 'append_elements' => array('c', 'd'),
+ );
+
+ $this->assertEquals(array('append_elements' => array('a', 'b', 'c', 'd')), $tree->merge($a, $b));
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/NormalizationTest.php b/console/skel/symfony/config/Tests/Definition/NormalizationTest.php
new file mode 100644
index 0000000..f011f54
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/NormalizationTest.php
@@ -0,0 +1,230 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\NodeInterface;
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+
+class NormalizationTest extends TestCase
+{
+ /**
+ * @dataProvider getEncoderTests
+ */
+ public function testNormalizeEncoders($denormalized)
+ {
+ $tb = new TreeBuilder();
+ $tree = $tb
+ ->root('root_name', 'array')
+ ->fixXmlConfig('encoder')
+ ->children()
+ ->node('encoders', 'array')
+ ->useAttributeAsKey('class')
+ ->prototype('array')
+ ->beforeNormalization()->ifString()->then(function ($v) { return array('algorithm' => $v); })->end()
+ ->children()
+ ->node('algorithm', 'scalar')->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->buildTree()
+ ;
+
+ $normalized = array(
+ 'encoders' => array(
+ 'foo' => array('algorithm' => 'plaintext'),
+ ),
+ );
+
+ $this->assertNormalized($tree, $denormalized, $normalized);
+ }
+
+ public function getEncoderTests()
+ {
+ $configs = array();
+
+ // XML
+ $configs[] = array(
+ 'encoder' => array(
+ array('class' => 'foo', 'algorithm' => 'plaintext'),
+ ),
+ );
+
+ // XML when only one element of this type
+ $configs[] = array(
+ 'encoder' => array('class' => 'foo', 'algorithm' => 'plaintext'),
+ );
+
+ // YAML/PHP
+ $configs[] = array(
+ 'encoders' => array(
+ array('class' => 'foo', 'algorithm' => 'plaintext'),
+ ),
+ );
+
+ // YAML/PHP
+ $configs[] = array(
+ 'encoders' => array(
+ 'foo' => 'plaintext',
+ ),
+ );
+
+ // YAML/PHP
+ $configs[] = array(
+ 'encoders' => array(
+ 'foo' => array('algorithm' => 'plaintext'),
+ ),
+ );
+
+ return array_map(function ($v) {
+ return array($v);
+ }, $configs);
+ }
+
+ /**
+ * @dataProvider getAnonymousKeysTests
+ */
+ public function testAnonymousKeysArray($denormalized)
+ {
+ $tb = new TreeBuilder();
+ $tree = $tb
+ ->root('root', 'array')
+ ->children()
+ ->node('logout', 'array')
+ ->fixXmlConfig('handler')
+ ->children()
+ ->node('handlers', 'array')
+ ->prototype('scalar')->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->buildTree()
+ ;
+
+ $normalized = array('logout' => array('handlers' => array('a', 'b', 'c')));
+
+ $this->assertNormalized($tree, $denormalized, $normalized);
+ }
+
+ public function getAnonymousKeysTests()
+ {
+ $configs = array();
+
+ $configs[] = array(
+ 'logout' => array(
+ 'handlers' => array('a', 'b', 'c'),
+ ),
+ );
+
+ $configs[] = array(
+ 'logout' => array(
+ 'handler' => array('a', 'b', 'c'),
+ ),
+ );
+
+ return array_map(function ($v) { return array($v); }, $configs);
+ }
+
+ /**
+ * @dataProvider getNumericKeysTests
+ */
+ public function testNumericKeysAsAttributes($denormalized)
+ {
+ $normalized = array(
+ 'thing' => array(42 => array('foo', 'bar'), 1337 => array('baz', 'qux')),
+ );
+
+ $this->assertNormalized($this->getNumericKeysTestTree(), $denormalized, $normalized);
+ }
+
+ public function getNumericKeysTests()
+ {
+ $configs = array();
+
+ $configs[] = array(
+ 'thing' => array(
+ 42 => array('foo', 'bar'), 1337 => array('baz', 'qux'),
+ ),
+ );
+
+ $configs[] = array(
+ 'thing' => array(
+ array('foo', 'bar', 'id' => 42), array('baz', 'qux', 'id' => 1337),
+ ),
+ );
+
+ return array_map(function ($v) { return array($v); }, $configs);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
+ * @expectedExceptionMessage The attribute "id" must be set for path "root.thing".
+ */
+ public function testNonAssociativeArrayThrowsExceptionIfAttributeNotSet()
+ {
+ $denormalized = array(
+ 'thing' => array(
+ array('foo', 'bar'), array('baz', 'qux'),
+ ),
+ );
+
+ $this->assertNormalized($this->getNumericKeysTestTree(), $denormalized, array());
+ }
+
+ public function testAssociativeArrayPreserveKeys()
+ {
+ $tb = new TreeBuilder();
+ $tree = $tb
+ ->root('root', 'array')
+ ->prototype('array')
+ ->children()
+ ->node('foo', 'scalar')->end()
+ ->end()
+ ->end()
+ ->end()
+ ->buildTree()
+ ;
+
+ $data = array('first' => array('foo' => 'bar'));
+
+ $this->assertNormalized($tree, $data, $data);
+ }
+
+ public static function assertNormalized(NodeInterface $tree, $denormalized, $normalized)
+ {
+ self::assertSame($normalized, $tree->normalize($denormalized));
+ }
+
+ private function getNumericKeysTestTree()
+ {
+ $tb = new TreeBuilder();
+ $tree = $tb
+ ->root('root', 'array')
+ ->children()
+ ->node('thing', 'array')
+ ->useAttributeAsKey('id')
+ ->prototype('array')
+ ->prototype('scalar')->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->buildTree()
+ ;
+
+ return $tree;
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/PrototypedArrayNodeTest.php b/console/skel/symfony/config/Tests/Definition/PrototypedArrayNodeTest.php
new file mode 100644
index 0000000..1a5de41
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/PrototypedArrayNodeTest.php
@@ -0,0 +1,342 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\PrototypedArrayNode;
+use Symfony\Component\Config\Definition\ArrayNode;
+use Symfony\Component\Config\Definition\ScalarNode;
+use Symfony\Component\Config\Definition\VariableNode;
+
+class PrototypedArrayNodeTest extends TestCase
+{
+ public function testGetDefaultValueReturnsAnEmptyArrayForPrototypes()
+ {
+ $node = new PrototypedArrayNode('root');
+ $prototype = new ArrayNode(null, $node);
+ $node->setPrototype($prototype);
+ $this->assertEmpty($node->getDefaultValue());
+ }
+
+ public function testGetDefaultValueReturnsDefaultValueForPrototypes()
+ {
+ $node = new PrototypedArrayNode('root');
+ $prototype = new ArrayNode(null, $node);
+ $node->setPrototype($prototype);
+ $node->setDefaultValue(array('test'));
+ $this->assertEquals(array('test'), $node->getDefaultValue());
+ }
+
+ // a remapped key (e.g. "mapping" -> "mappings") should be unset after being used
+ public function testRemappedKeysAreUnset()
+ {
+ $node = new ArrayNode('root');
+ $mappingsNode = new PrototypedArrayNode('mappings');
+ $node->addChild($mappingsNode);
+
+ // each item under mappings is just a scalar
+ $prototype = new ScalarNode(null, $mappingsNode);
+ $mappingsNode->setPrototype($prototype);
+
+ $remappings = array();
+ $remappings[] = array('mapping', 'mappings');
+ $node->setXmlRemappings($remappings);
+
+ $normalized = $node->normalize(array('mapping' => array('foo', 'bar')));
+ $this->assertEquals(array('mappings' => array('foo', 'bar')), $normalized);
+ }
+
+ /**
+ * Tests that when a key attribute is mapped, that key is removed from the array.
+ *
+ *
+ *
+ *
+ *
+ *
+ * The above should finally be mapped to an array that looks like this
+ * (because "id" is the key attribute).
+ *
+ * array(
+ * 'things' => array(
+ * 'option1' => 'foo',
+ * 'option2' => 'bar',
+ * )
+ * )
+ */
+ public function testMappedAttributeKeyIsRemoved()
+ {
+ $node = new PrototypedArrayNode('root');
+ $node->setKeyAttribute('id', true);
+
+ // each item under the root is an array, with one scalar item
+ $prototype = new ArrayNode(null, $node);
+ $prototype->addChild(new ScalarNode('foo'));
+ $node->setPrototype($prototype);
+
+ $children = array();
+ $children[] = array('id' => 'item_name', 'foo' => 'bar');
+ $normalized = $node->normalize($children);
+
+ $expected = array();
+ $expected['item_name'] = array('foo' => 'bar');
+ $this->assertEquals($expected, $normalized);
+ }
+
+ /**
+ * Tests the opposite of the testMappedAttributeKeyIsRemoved because
+ * the removal can be toggled with an option.
+ */
+ public function testMappedAttributeKeyNotRemoved()
+ {
+ $node = new PrototypedArrayNode('root');
+ $node->setKeyAttribute('id', false);
+
+ // each item under the root is an array, with two scalar items
+ $prototype = new ArrayNode(null, $node);
+ $prototype->addChild(new ScalarNode('foo'));
+ $prototype->addChild(new ScalarNode('id')); // the key attribute will remain
+ $node->setPrototype($prototype);
+
+ $children = array();
+ $children[] = array('id' => 'item_name', 'foo' => 'bar');
+ $normalized = $node->normalize($children);
+
+ $expected = array();
+ $expected['item_name'] = array('id' => 'item_name', 'foo' => 'bar');
+ $this->assertEquals($expected, $normalized);
+ }
+
+ public function testAddDefaultChildren()
+ {
+ $node = $this->getPrototypeNodeWithDefaultChildren();
+ $node->setAddChildrenIfNoneSet();
+ $this->assertTrue($node->hasDefaultValue());
+ $this->assertEquals(array(array('foo' => 'bar')), $node->getDefaultValue());
+
+ $node = $this->getPrototypeNodeWithDefaultChildren();
+ $node->setKeyAttribute('foobar');
+ $node->setAddChildrenIfNoneSet();
+ $this->assertTrue($node->hasDefaultValue());
+ $this->assertEquals(array('defaults' => array('foo' => 'bar')), $node->getDefaultValue());
+
+ $node = $this->getPrototypeNodeWithDefaultChildren();
+ $node->setKeyAttribute('foobar');
+ $node->setAddChildrenIfNoneSet('defaultkey');
+ $this->assertTrue($node->hasDefaultValue());
+ $this->assertEquals(array('defaultkey' => array('foo' => 'bar')), $node->getDefaultValue());
+
+ $node = $this->getPrototypeNodeWithDefaultChildren();
+ $node->setKeyAttribute('foobar');
+ $node->setAddChildrenIfNoneSet(array('defaultkey'));
+ $this->assertTrue($node->hasDefaultValue());
+ $this->assertEquals(array('defaultkey' => array('foo' => 'bar')), $node->getDefaultValue());
+
+ $node = $this->getPrototypeNodeWithDefaultChildren();
+ $node->setKeyAttribute('foobar');
+ $node->setAddChildrenIfNoneSet(array('dk1', 'dk2'));
+ $this->assertTrue($node->hasDefaultValue());
+ $this->assertEquals(array('dk1' => array('foo' => 'bar'), 'dk2' => array('foo' => 'bar')), $node->getDefaultValue());
+
+ $node = $this->getPrototypeNodeWithDefaultChildren();
+ $node->setAddChildrenIfNoneSet(array(5, 6));
+ $this->assertTrue($node->hasDefaultValue());
+ $this->assertEquals(array(0 => array('foo' => 'bar'), 1 => array('foo' => 'bar')), $node->getDefaultValue());
+
+ $node = $this->getPrototypeNodeWithDefaultChildren();
+ $node->setAddChildrenIfNoneSet(2);
+ $this->assertTrue($node->hasDefaultValue());
+ $this->assertEquals(array(array('foo' => 'bar'), array('foo' => 'bar')), $node->getDefaultValue());
+ }
+
+ public function testDefaultChildrenWinsOverDefaultValue()
+ {
+ $node = $this->getPrototypeNodeWithDefaultChildren();
+ $node->setAddChildrenIfNoneSet();
+ $node->setDefaultValue(array('bar' => 'foo'));
+ $this->assertTrue($node->hasDefaultValue());
+ $this->assertEquals(array(array('foo' => 'bar')), $node->getDefaultValue());
+ }
+
+ protected function getPrototypeNodeWithDefaultChildren()
+ {
+ $node = new PrototypedArrayNode('root');
+ $prototype = new ArrayNode(null, $node);
+ $child = new ScalarNode('foo');
+ $child->setDefaultValue('bar');
+ $prototype->addChild($child);
+ $prototype->setAddIfNotSet(true);
+ $node->setPrototype($prototype);
+
+ return $node;
+ }
+
+ /**
+ * Tests that when a key attribute is mapped, that key is removed from the array.
+ * And if only 'value' element is left in the array, it will replace its wrapper array.
+ *
+ *
+ *
+ *
+ *
+ * The above should finally be mapped to an array that looks like this
+ * (because "id" is the key attribute).
+ *
+ * array(
+ * 'things' => array(
+ * 'option1' => 'value1'
+ * )
+ * )
+ *
+ * It's also possible to mix 'value-only' and 'non-value-only' elements in the array.
+ *
+ *
+ *
+ *
+ *
+ *
+ * The above should finally be mapped to an array as follows
+ *
+ * array(
+ * 'things' => array(
+ * 'option1' => 'value1',
+ * 'option2' => array(
+ * 'value' => 'value2',
+ * 'foo' => 'foo2'
+ * )
+ * )
+ * )
+ *
+ * The 'value' element can also be ArrayNode:
+ *
+ *
+ *
+ *
+ * foo1
+ * bar1
+ *
+ *
+ *
+ *
+ * The above should be finally be mapped to an array as follows
+ *
+ * array(
+ * 'things' => array(
+ * 'option1' => array(
+ * 'foo' => 'foo1',
+ * 'bar' => 'bar1'
+ * )
+ * )
+ * )
+ *
+ * If using VariableNode for value node, it's also possible to mix different types of value nodes:
+ *
+ *
+ *
+ *
+ * foo1
+ * bar1
+ *
+ *
+ *
+ *
+ *
+ * The above should be finally mapped to an array as follows
+ *
+ * array(
+ * 'things' => array(
+ * 'option1' => array(
+ * 'foo' => 'foo1',
+ * 'bar' => 'bar1'
+ * ),
+ * 'option2' => 'value2'
+ * )
+ * )
+ *
+ *
+ * @dataProvider getDataForKeyRemovedLeftValueOnly
+ */
+ public function testMappedAttributeKeyIsRemovedLeftValueOnly($value, $children, $expected)
+ {
+ $node = new PrototypedArrayNode('root');
+ $node->setKeyAttribute('id', true);
+
+ // each item under the root is an array, with one scalar item
+ $prototype = new ArrayNode(null, $node);
+ $prototype->addChild(new ScalarNode('id'));
+ $prototype->addChild(new ScalarNode('foo'));
+ $prototype->addChild($value);
+ $node->setPrototype($prototype);
+
+ $normalized = $node->normalize($children);
+ $this->assertEquals($expected, $normalized);
+ }
+
+ public function getDataForKeyRemovedLeftValueOnly()
+ {
+ $scalarValue = new ScalarNode('value');
+
+ $arrayValue = new ArrayNode('value');
+ $arrayValue->addChild(new ScalarNode('foo'));
+ $arrayValue->addChild(new ScalarNode('bar'));
+
+ $variableValue = new VariableNode('value');
+
+ return array(
+ array(
+ $scalarValue,
+ array(
+ array('id' => 'option1', 'value' => 'value1'),
+ ),
+ array('option1' => 'value1'),
+ ),
+
+ array(
+ $scalarValue,
+ array(
+ array('id' => 'option1', 'value' => 'value1'),
+ array('id' => 'option2', 'value' => 'value2', 'foo' => 'foo2'),
+ ),
+ array(
+ 'option1' => 'value1',
+ 'option2' => array('value' => 'value2', 'foo' => 'foo2'),
+ ),
+ ),
+
+ array(
+ $arrayValue,
+ array(
+ array(
+ 'id' => 'option1',
+ 'value' => array('foo' => 'foo1', 'bar' => 'bar1'),
+ ),
+ ),
+ array(
+ 'option1' => array('foo' => 'foo1', 'bar' => 'bar1'),
+ ),
+ ),
+
+ array($variableValue,
+ array(
+ array(
+ 'id' => 'option1', 'value' => array('foo' => 'foo1', 'bar' => 'bar1'),
+ ),
+ array('id' => 'option2', 'value' => 'value2'),
+ ),
+ array(
+ 'option1' => array('foo' => 'foo1', 'bar' => 'bar1'),
+ 'option2' => 'value2',
+ ),
+ ),
+ );
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Definition/ScalarNodeTest.php b/console/skel/symfony/config/Tests/Definition/ScalarNodeTest.php
new file mode 100644
index 0000000..a402a61
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Definition/ScalarNodeTest.php
@@ -0,0 +1,137 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\ScalarNode;
+
+class ScalarNodeTest extends TestCase
+{
+ /**
+ * @dataProvider getValidValues
+ */
+ public function testNormalize($value)
+ {
+ $node = new ScalarNode('test');
+ $this->assertSame($value, $node->normalize($value));
+ }
+
+ public function getValidValues()
+ {
+ return array(
+ array(false),
+ array(true),
+ array(null),
+ array(''),
+ array('foo'),
+ array(0),
+ array(1),
+ array(0.0),
+ array(0.1),
+ );
+ }
+
+ /**
+ * @dataProvider getInvalidValues
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException
+ */
+ public function testNormalizeThrowsExceptionOnInvalidValues($value)
+ {
+ $node = new ScalarNode('test');
+ $node->normalize($value);
+ }
+
+ public function getInvalidValues()
+ {
+ return array(
+ array(array()),
+ array(array('foo' => 'bar')),
+ array(new \stdClass()),
+ );
+ }
+
+ public function testNormalizeThrowsExceptionWithoutHint()
+ {
+ $node = new ScalarNode('test');
+
+ if (method_exists($this, 'expectException')) {
+ $this->expectException('Symfony\Component\Config\Definition\Exception\InvalidTypeException');
+ $this->expectExceptionMessage('Invalid type for path "test". Expected scalar, but got array.');
+ } else {
+ $this->setExpectedException('Symfony\Component\Config\Definition\Exception\InvalidTypeException', 'Invalid type for path "test". Expected scalar, but got array.');
+ }
+
+ $node->normalize(array());
+ }
+
+ public function testNormalizeThrowsExceptionWithErrorMessage()
+ {
+ $node = new ScalarNode('test');
+ $node->setInfo('"the test value"');
+
+ if (method_exists($this, 'expectException')) {
+ $this->expectException('Symfony\Component\Config\Definition\Exception\InvalidTypeException');
+ $this->expectExceptionMessage("Invalid type for path \"test\". Expected scalar, but got array.\nHint: \"the test value\"");
+ } else {
+ $this->setExpectedException('Symfony\Component\Config\Definition\Exception\InvalidTypeException', "Invalid type for path \"test\". Expected scalar, but got array.\nHint: \"the test value\"");
+ }
+
+ $node->normalize(array());
+ }
+
+ /**
+ * @dataProvider getValidNonEmptyValues
+ *
+ * @param mixed $value
+ */
+ public function testValidNonEmptyValues($value)
+ {
+ $node = new ScalarNode('test');
+ $node->setAllowEmptyValue(false);
+
+ $this->assertSame($value, $node->finalize($value));
+ }
+
+ public function getValidNonEmptyValues()
+ {
+ return array(
+ array(false),
+ array(true),
+ array('foo'),
+ array(0),
+ array(1),
+ array(0.0),
+ array(0.1),
+ );
+ }
+
+ /**
+ * @dataProvider getEmptyValues
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
+ *
+ * @param mixed $value
+ */
+ public function testNotAllowedEmptyValuesThrowException($value)
+ {
+ $node = new ScalarNode('test');
+ $node->setAllowEmptyValue(false);
+ $node->finalize($value);
+ }
+
+ public function getEmptyValues()
+ {
+ return array(
+ array(null),
+ array(''),
+ );
+ }
+}
diff --git a/console/skel/symfony/config/Tests/DependencyInjection/ConfigCachePassTest.php b/console/skel/symfony/config/Tests/DependencyInjection/ConfigCachePassTest.php
new file mode 100644
index 0000000..7452755
--- /dev/null
+++ b/console/skel/symfony/config/Tests/DependencyInjection/ConfigCachePassTest.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\Config\DependencyInjection\ConfigCachePass;
+
+class ConfigCachePassTest extends TestCase
+{
+ public function testThatCheckersAreProcessedInPriorityOrder()
+ {
+ $container = new ContainerBuilder();
+
+ $definition = $container->register('config_cache_factory')->addArgument(null);
+ $container->register('checker_2')->addTag('config_cache.resource_checker', array('priority' => 100));
+ $container->register('checker_1')->addTag('config_cache.resource_checker', array('priority' => 200));
+ $container->register('checker_3')->addTag('config_cache.resource_checker');
+
+ $pass = new ConfigCachePass();
+ $pass->process($container);
+
+ $expected = new IteratorArgument(array(
+ new Reference('checker_1'),
+ new Reference('checker_2'),
+ new Reference('checker_3'),
+ ));
+ $this->assertEquals($expected, $definition->getArgument(0));
+ }
+
+ public function testThatCheckersCanBeMissing()
+ {
+ $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('findTaggedServiceIds'))->getMock();
+
+ $container->expects($this->atLeastOnce())
+ ->method('findTaggedServiceIds')
+ ->will($this->returnValue(array()));
+
+ $pass = new ConfigCachePass();
+ $pass->process($container);
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Exception/FileLoaderLoadExceptionTest.php b/console/skel/symfony/config/Tests/Exception/FileLoaderLoadExceptionTest.php
new file mode 100644
index 0000000..7c5e167
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Exception/FileLoaderLoadExceptionTest.php
@@ -0,0 +1,98 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Exception;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Exception\FileLoaderLoadException;
+
+class FileLoaderLoadExceptionTest extends TestCase
+{
+ public function testMessageCannotLoadResource()
+ {
+ $exception = new FileLoaderLoadException('resource', null);
+ $this->assertEquals('Cannot load resource "resource".', $exception->getMessage());
+ }
+
+ public function testMessageCannotLoadResourceWithType()
+ {
+ $exception = new FileLoaderLoadException('resource', null, null, null, 'foobar');
+ $this->assertEquals('Cannot load resource "resource". Make sure there is a loader supporting the "foobar" type.', $exception->getMessage());
+ }
+
+ public function testMessageCannotLoadResourceWithAnnotationType()
+ {
+ $exception = new FileLoaderLoadException('resource', null, null, null, 'annotation');
+ $this->assertEquals('Cannot load resource "resource". Make sure annotations are enabled.', $exception->getMessage());
+ }
+
+ public function testMessageCannotImportResourceFromSource()
+ {
+ $exception = new FileLoaderLoadException('resource', 'sourceResource');
+ $this->assertEquals('Cannot import resource "resource" from "sourceResource".', $exception->getMessage());
+ }
+
+ public function testMessageCannotImportBundleResource()
+ {
+ $exception = new FileLoaderLoadException('@resource', 'sourceResource');
+ $this->assertEquals(
+ 'Cannot import resource "@resource" from "sourceResource". '.
+ 'Make sure the "resource" bundle is correctly registered and loaded in the application kernel class. '.
+ 'If the bundle is registered, make sure the bundle path "@resource" is not empty.',
+ $exception->getMessage()
+ );
+ }
+
+ public function testMessageHasPreviousErrorWithDotAndUnableToLoad()
+ {
+ $exception = new FileLoaderLoadException(
+ 'resource',
+ null,
+ null,
+ new \Exception('There was a previous error with an ending dot.')
+ );
+ $this->assertEquals(
+ 'There was a previous error with an ending dot in resource (which is loaded in resource "resource").',
+ $exception->getMessage()
+ );
+ }
+
+ public function testMessageHasPreviousErrorWithoutDotAndUnableToLoad()
+ {
+ $exception = new FileLoaderLoadException(
+ 'resource',
+ null,
+ null,
+ new \Exception('There was a previous error with no ending dot')
+ );
+ $this->assertEquals(
+ 'There was a previous error with no ending dot in resource (which is loaded in resource "resource").',
+ $exception->getMessage()
+ );
+ }
+
+ public function testMessageHasPreviousErrorAndUnableToLoadBundle()
+ {
+ $exception = new FileLoaderLoadException(
+ '@resource',
+ null,
+ null,
+ new \Exception('There was a previous error with an ending dot.')
+ );
+ $this->assertEquals(
+ 'There was a previous error with an ending dot in @resource '.
+ '(which is loaded in resource "@resource"). '.
+ 'Make sure the "resource" bundle is correctly registered and loaded in the application kernel class. '.
+ 'If the bundle is registered, make sure the bundle path "@resource" is not empty.',
+ $exception->getMessage()
+ );
+ }
+}
diff --git a/console/skel/symfony/config/Tests/FileLocatorTest.php b/console/skel/symfony/config/Tests/FileLocatorTest.php
new file mode 100644
index 0000000..049c009
--- /dev/null
+++ b/console/skel/symfony/config/Tests/FileLocatorTest.php
@@ -0,0 +1,120 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\FileLocator;
+
+class FileLocatorTest extends TestCase
+{
+ /**
+ * @dataProvider getIsAbsolutePathTests
+ */
+ public function testIsAbsolutePath($path)
+ {
+ $loader = new FileLocator(array());
+ $r = new \ReflectionObject($loader);
+ $m = $r->getMethod('isAbsolutePath');
+ $m->setAccessible(true);
+
+ $this->assertTrue($m->invoke($loader, $path), '->isAbsolutePath() returns true for an absolute path');
+ }
+
+ public function getIsAbsolutePathTests()
+ {
+ return array(
+ array('/foo.xml'),
+ array('c:\\\\foo.xml'),
+ array('c:/foo.xml'),
+ array('\\server\\foo.xml'),
+ array('https://server/foo.xml'),
+ array('phar://server/foo.xml'),
+ );
+ }
+
+ public function testLocate()
+ {
+ $loader = new FileLocator(__DIR__.'/Fixtures');
+
+ $this->assertEquals(
+ __DIR__.DIRECTORY_SEPARATOR.'FileLocatorTest.php',
+ $loader->locate('FileLocatorTest.php', __DIR__),
+ '->locate() returns the absolute filename if the file exists in the given path'
+ );
+
+ $this->assertEquals(
+ __DIR__.'/Fixtures'.DIRECTORY_SEPARATOR.'foo.xml',
+ $loader->locate('foo.xml', __DIR__),
+ '->locate() returns the absolute filename if the file exists in one of the paths given in the constructor'
+ );
+
+ $this->assertEquals(
+ __DIR__.'/Fixtures'.DIRECTORY_SEPARATOR.'foo.xml',
+ $loader->locate(__DIR__.'/Fixtures'.DIRECTORY_SEPARATOR.'foo.xml', __DIR__),
+ '->locate() returns the absolute filename if the file exists in one of the paths given in the constructor'
+ );
+
+ $loader = new FileLocator(array(__DIR__.'/Fixtures', __DIR__.'/Fixtures/Again'));
+
+ $this->assertEquals(
+ array(__DIR__.'/Fixtures'.DIRECTORY_SEPARATOR.'foo.xml', __DIR__.'/Fixtures/Again'.DIRECTORY_SEPARATOR.'foo.xml'),
+ $loader->locate('foo.xml', __DIR__, false),
+ '->locate() returns an array of absolute filenames'
+ );
+
+ $this->assertEquals(
+ array(__DIR__.'/Fixtures'.DIRECTORY_SEPARATOR.'foo.xml', __DIR__.'/Fixtures/Again'.DIRECTORY_SEPARATOR.'foo.xml'),
+ $loader->locate('foo.xml', __DIR__.'/Fixtures', false),
+ '->locate() returns an array of absolute filenames'
+ );
+
+ $loader = new FileLocator(__DIR__.'/Fixtures/Again');
+
+ $this->assertEquals(
+ array(__DIR__.'/Fixtures'.DIRECTORY_SEPARATOR.'foo.xml', __DIR__.'/Fixtures/Again'.DIRECTORY_SEPARATOR.'foo.xml'),
+ $loader->locate('foo.xml', __DIR__.'/Fixtures', false),
+ '->locate() returns an array of absolute filenames'
+ );
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Exception\FileLocatorFileNotFoundException
+ * @expectedExceptionMessage The file "foobar.xml" does not exist
+ */
+ public function testLocateThrowsAnExceptionIfTheFileDoesNotExists()
+ {
+ $loader = new FileLocator(array(__DIR__.'/Fixtures'));
+
+ $loader->locate('foobar.xml', __DIR__);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Exception\FileLocatorFileNotFoundException
+ */
+ public function testLocateThrowsAnExceptionIfTheFileDoesNotExistsInAbsolutePath()
+ {
+ $loader = new FileLocator(array(__DIR__.'/Fixtures'));
+
+ $loader->locate(__DIR__.'/Fixtures/foobar.xml', __DIR__);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage An empty file name is not valid to be located.
+ */
+ public function testLocateEmpty()
+ {
+ $loader = new FileLocator(array(__DIR__.'/Fixtures'));
+
+ $loader->locate(null, __DIR__);
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Fixtures/Again/foo.xml b/console/skel/symfony/config/Tests/Fixtures/Again/foo.xml
new file mode 100644
index 0000000..e69de29
diff --git a/console/skel/symfony/config/Tests/Fixtures/BarNode.php b/console/skel/symfony/config/Tests/Fixtures/BarNode.php
new file mode 100644
index 0000000..0b9c32d
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Fixtures/BarNode.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Fixtures;
+
+use Symfony\Component\Config\Definition\ArrayNode;
+
+class BarNode extends ArrayNode
+{
+}
diff --git a/console/skel/symfony/config/Tests/Fixtures/Builder/BarNodeDefinition.php b/console/skel/symfony/config/Tests/Fixtures/Builder/BarNodeDefinition.php
new file mode 100644
index 0000000..0d46f3d
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Fixtures/Builder/BarNodeDefinition.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition\Builder;
+
+use Symfony\Component\Config\Definition\Builder\NodeDefinition;
+use Symfony\Component\Config\Tests\Fixtures\BarNode;
+
+class BarNodeDefinition extends NodeDefinition
+{
+ protected function createNode()
+ {
+ return new BarNode($this->name);
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Fixtures/Builder/NodeBuilder.php b/console/skel/symfony/config/Tests/Fixtures/Builder/NodeBuilder.php
new file mode 100644
index 0000000..aa59863
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Fixtures/Builder/NodeBuilder.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition\Builder;
+
+use Symfony\Component\Config\Definition\Builder\NodeBuilder as BaseNodeBuilder;
+
+class NodeBuilder extends BaseNodeBuilder
+{
+ public function barNode($name)
+ {
+ return $this->node($name, 'bar');
+ }
+
+ protected function getNodeClass($type)
+ {
+ switch ($type) {
+ case 'variable':
+ return __NAMESPACE__.'\\'.ucfirst($type).'NodeDefinition';
+ case 'bar':
+ return __NAMESPACE__.'\\'.ucfirst($type).'NodeDefinition';
+ default:
+ return parent::getNodeClass($type);
+ }
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Fixtures/Builder/VariableNodeDefinition.php b/console/skel/symfony/config/Tests/Fixtures/Builder/VariableNodeDefinition.php
new file mode 100644
index 0000000..1017880
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Fixtures/Builder/VariableNodeDefinition.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Definition\Builder;
+
+use Symfony\Component\Config\Definition\Builder\VariableNodeDefinition as BaseVariableNodeDefinition;
+
+class VariableNodeDefinition extends BaseVariableNodeDefinition
+{
+}
diff --git a/console/skel/symfony/config/Tests/Fixtures/Configuration/ExampleConfiguration.php b/console/skel/symfony/config/Tests/Fixtures/Configuration/ExampleConfiguration.php
new file mode 100644
index 0000000..3a34f90
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Fixtures/Configuration/ExampleConfiguration.php
@@ -0,0 +1,100 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Fixtures\Configuration;
+
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+
+class ExampleConfiguration implements ConfigurationInterface
+{
+ public function getConfigTreeBuilder()
+ {
+ $treeBuilder = new TreeBuilder();
+ $rootNode = $treeBuilder->root('acme_root');
+
+ $rootNode
+ ->fixXmlConfig('parameter')
+ ->fixXmlConfig('connection')
+ ->fixXmlConfig('cms_page')
+ ->children()
+ ->booleanNode('boolean')->defaultTrue()->end()
+ ->scalarNode('scalar_empty')->end()
+ ->scalarNode('scalar_null')->defaultNull()->end()
+ ->scalarNode('scalar_true')->defaultTrue()->end()
+ ->scalarNode('scalar_false')->defaultFalse()->end()
+ ->scalarNode('scalar_default')->defaultValue('default')->end()
+ ->scalarNode('scalar_array_empty')->defaultValue(array())->end()
+ ->scalarNode('scalar_array_defaults')->defaultValue(array('elem1', 'elem2'))->end()
+ ->scalarNode('scalar_required')->isRequired()->end()
+ ->scalarNode('node_with_a_looong_name')->end()
+ ->enumNode('enum_with_default')->values(array('this', 'that'))->defaultValue('this')->end()
+ ->enumNode('enum')->values(array('this', 'that'))->end()
+ ->arrayNode('array')
+ ->info('some info')
+ ->canBeUnset()
+ ->children()
+ ->scalarNode('child1')->end()
+ ->scalarNode('child2')->end()
+ ->scalarNode('child3')
+ ->info(
+ "this is a long\n".
+ "multi-line info text\n".
+ 'which should be indented'
+ )
+ ->example('example setting')
+ ->end()
+ ->end()
+ ->end()
+ ->arrayNode('scalar_prototyped')
+ ->prototype('scalar')->end()
+ ->end()
+ ->arrayNode('parameters')
+ ->useAttributeAsKey('name')
+ ->prototype('scalar')->info('Parameter name')->end()
+ ->end()
+ ->arrayNode('connections')
+ ->prototype('array')
+ ->children()
+ ->scalarNode('user')->end()
+ ->scalarNode('pass')->end()
+ ->end()
+ ->end()
+ ->end()
+ ->arrayNode('cms_pages')
+ ->useAttributeAsKey('page')
+ ->prototype('array')
+ ->useAttributeAsKey('locale')
+ ->prototype('array')
+ ->children()
+ ->scalarNode('title')->isRequired()->end()
+ ->scalarNode('path')->isRequired()->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->arrayNode('pipou')
+ ->useAttributeAsKey('name')
+ ->prototype('array')
+ ->prototype('array')
+ ->children()
+ ->scalarNode('didou')
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ;
+
+ return $treeBuilder;
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Fixtures/Resource/.hiddenFile b/console/skel/symfony/config/Tests/Fixtures/Resource/.hiddenFile
new file mode 100644
index 0000000..e69de29
diff --git a/console/skel/symfony/config/Tests/Fixtures/Resource/ConditionalClass.php b/console/skel/symfony/config/Tests/Fixtures/Resource/ConditionalClass.php
new file mode 100644
index 0000000..2ba48c5
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Fixtures/Resource/ConditionalClass.php
@@ -0,0 +1,9 @@
+
+]>
+
diff --git a/console/skel/symfony/config/Tests/Fixtures/Util/invalid.xml b/console/skel/symfony/config/Tests/Fixtures/Util/invalid.xml
new file mode 100644
index 0000000..a07af9f
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Fixtures/Util/invalid.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/console/skel/symfony/config/Tests/Fixtures/Util/invalid_schema.xml b/console/skel/symfony/config/Tests/Fixtures/Util/invalid_schema.xml
new file mode 100644
index 0000000..e2725a2
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Fixtures/Util/invalid_schema.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/console/skel/symfony/config/Tests/Fixtures/Util/schema.xsd b/console/skel/symfony/config/Tests/Fixtures/Util/schema.xsd
new file mode 100644
index 0000000..e56820f
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Fixtures/Util/schema.xsd
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/console/skel/symfony/config/Tests/Fixtures/Util/valid.xml b/console/skel/symfony/config/Tests/Fixtures/Util/valid.xml
new file mode 100644
index 0000000..a96bb38
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Fixtures/Util/valid.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/console/skel/symfony/config/Tests/Fixtures/foo.xml b/console/skel/symfony/config/Tests/Fixtures/foo.xml
new file mode 100644
index 0000000..e69de29
diff --git a/console/skel/symfony/config/Tests/Loader/DelegatingLoaderTest.php b/console/skel/symfony/config/Tests/Loader/DelegatingLoaderTest.php
new file mode 100644
index 0000000..4a01d26
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Loader/DelegatingLoaderTest.php
@@ -0,0 +1,71 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Loader\LoaderResolver;
+use Symfony\Component\Config\Loader\DelegatingLoader;
+
+class DelegatingLoaderTest extends TestCase
+{
+ public function testConstructor()
+ {
+ $loader = new DelegatingLoader($resolver = new LoaderResolver());
+ $this->assertTrue(true, '__construct() takes a loader resolver as its first argument');
+ }
+
+ public function testGetSetResolver()
+ {
+ $resolver = new LoaderResolver();
+ $loader = new DelegatingLoader($resolver);
+ $this->assertSame($resolver, $loader->getResolver(), '->getResolver() gets the resolver loader');
+ $loader->setResolver($resolver = new LoaderResolver());
+ $this->assertSame($resolver, $loader->getResolver(), '->setResolver() sets the resolver loader');
+ }
+
+ public function testSupports()
+ {
+ $loader1 = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock();
+ $loader1->expects($this->once())->method('supports')->will($this->returnValue(true));
+ $loader = new DelegatingLoader(new LoaderResolver(array($loader1)));
+ $this->assertTrue($loader->supports('foo.xml'), '->supports() returns true if the resource is loadable');
+
+ $loader1 = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock();
+ $loader1->expects($this->once())->method('supports')->will($this->returnValue(false));
+ $loader = new DelegatingLoader(new LoaderResolver(array($loader1)));
+ $this->assertFalse($loader->supports('foo.foo'), '->supports() returns false if the resource is not loadable');
+ }
+
+ public function testLoad()
+ {
+ $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock();
+ $loader->expects($this->once())->method('supports')->will($this->returnValue(true));
+ $loader->expects($this->once())->method('load');
+ $resolver = new LoaderResolver(array($loader));
+ $loader = new DelegatingLoader($resolver);
+
+ $loader->load('foo');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Exception\FileLoaderLoadException
+ */
+ public function testLoadThrowsAnExceptionIfTheResourceCannotBeLoaded()
+ {
+ $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock();
+ $loader->expects($this->once())->method('supports')->will($this->returnValue(false));
+ $resolver = new LoaderResolver(array($loader));
+ $loader = new DelegatingLoader($resolver);
+
+ $loader->load('foo');
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Loader/FileLoaderTest.php b/console/skel/symfony/config/Tests/Loader/FileLoaderTest.php
new file mode 100644
index 0000000..c6e283c
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Loader/FileLoaderTest.php
@@ -0,0 +1,128 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\Config\Loader\FileLoader;
+use Symfony\Component\Config\Loader\LoaderResolver;
+
+class FileLoaderTest extends TestCase
+{
+ public function testImportWithFileLocatorDelegation()
+ {
+ $locatorMock = $this->getMockBuilder('Symfony\Component\Config\FileLocatorInterface')->getMock();
+
+ $locatorMockForAdditionalLoader = $this->getMockBuilder('Symfony\Component\Config\FileLocatorInterface')->getMock();
+ $locatorMockForAdditionalLoader->expects($this->any())->method('locate')->will($this->onConsecutiveCalls(
+ array('path/to/file1'), // Default
+ array('path/to/file1', 'path/to/file2'), // First is imported
+ array('path/to/file1', 'path/to/file2'), // Second is imported
+ array('path/to/file1'), // Exception
+ array('path/to/file1', 'path/to/file2') // Exception
+ ));
+
+ $fileLoader = new TestFileLoader($locatorMock);
+ $fileLoader->setSupports(false);
+ $fileLoader->setCurrentDir('.');
+
+ $additionalLoader = new TestFileLoader($locatorMockForAdditionalLoader);
+ $additionalLoader->setCurrentDir('.');
+
+ $fileLoader->setResolver($loaderResolver = new LoaderResolver(array($fileLoader, $additionalLoader)));
+
+ // Default case
+ $this->assertSame('path/to/file1', $fileLoader->import('my_resource'));
+
+ // Check first file is imported if not already loading
+ $this->assertSame('path/to/file1', $fileLoader->import('my_resource'));
+
+ // Check second file is imported if first is already loading
+ $fileLoader->addLoading('path/to/file1');
+ $this->assertSame('path/to/file2', $fileLoader->import('my_resource'));
+
+ // Check exception throws if first (and only available) file is already loading
+ try {
+ $fileLoader->import('my_resource');
+ $this->fail('->import() throws a FileLoaderImportCircularReferenceException if the resource is already loading');
+ } catch (\Exception $e) {
+ $this->assertInstanceOf('Symfony\Component\Config\Exception\FileLoaderImportCircularReferenceException', $e, '->import() throws a FileLoaderImportCircularReferenceException if the resource is already loading');
+ }
+
+ // Check exception throws if all files are already loading
+ try {
+ $fileLoader->addLoading('path/to/file2');
+ $fileLoader->import('my_resource');
+ $this->fail('->import() throws a FileLoaderImportCircularReferenceException if the resource is already loading');
+ } catch (\Exception $e) {
+ $this->assertInstanceOf('Symfony\Component\Config\Exception\FileLoaderImportCircularReferenceException', $e, '->import() throws a FileLoaderImportCircularReferenceException if the resource is already loading');
+ }
+ }
+
+ public function testImportWithGlobLikeResource()
+ {
+ $locatorMock = $this->getMockBuilder('Symfony\Component\Config\FileLocatorInterface')->getMock();
+ $loader = new TestFileLoader($locatorMock);
+
+ $this->assertSame('[foo]', $loader->import('[foo]'));
+ }
+
+ public function testImportWithNoGlobMatch()
+ {
+ $locatorMock = $this->getMockBuilder('Symfony\Component\Config\FileLocatorInterface')->getMock();
+ $loader = new TestFileLoader($locatorMock);
+
+ $this->assertNull($loader->import('./*.abc'));
+ }
+
+ public function testImportWithSimpleGlob()
+ {
+ $loader = new TestFileLoader(new FileLocator(__DIR__));
+
+ $this->assertSame(__FILE__, strtr($loader->import('FileLoaderTest.*'), '/', DIRECTORY_SEPARATOR));
+ }
+}
+
+class TestFileLoader extends FileLoader
+{
+ private $supports = true;
+
+ public function load($resource, $type = null)
+ {
+ return $resource;
+ }
+
+ public function supports($resource, $type = null)
+ {
+ return $this->supports;
+ }
+
+ public function addLoading($resource)
+ {
+ self::$loading[$resource] = true;
+ }
+
+ public function removeLoading($resource)
+ {
+ unset(self::$loading[$resource]);
+ }
+
+ public function clearLoading()
+ {
+ self::$loading = array();
+ }
+
+ public function setSupports($supports)
+ {
+ $this->supports = $supports;
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Loader/LoaderResolverTest.php b/console/skel/symfony/config/Tests/Loader/LoaderResolverTest.php
new file mode 100644
index 0000000..0bf56b6
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Loader/LoaderResolverTest.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Loader\LoaderResolver;
+
+class LoaderResolverTest extends TestCase
+{
+ public function testConstructor()
+ {
+ $resolver = new LoaderResolver(array(
+ $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(),
+ ));
+
+ $this->assertEquals(array($loader), $resolver->getLoaders(), '__construct() takes an array of loaders as its first argument');
+ }
+
+ public function testResolve()
+ {
+ $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock();
+ $resolver = new LoaderResolver(array($loader));
+ $this->assertFalse($resolver->resolve('foo.foo'), '->resolve() returns false if no loader is able to load the resource');
+
+ $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock();
+ $loader->expects($this->once())->method('supports')->will($this->returnValue(true));
+ $resolver = new LoaderResolver(array($loader));
+ $this->assertEquals($loader, $resolver->resolve(function () {}), '->resolve() returns the loader for the given resource');
+ }
+
+ public function testLoaders()
+ {
+ $resolver = new LoaderResolver();
+ $resolver->addLoader($loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock());
+
+ $this->assertEquals(array($loader), $resolver->getLoaders(), 'addLoader() adds a loader');
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Loader/LoaderTest.php b/console/skel/symfony/config/Tests/Loader/LoaderTest.php
new file mode 100644
index 0000000..fefb1d7
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Loader/LoaderTest.php
@@ -0,0 +1,118 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Loader\Loader;
+
+class LoaderTest extends TestCase
+{
+ public function testGetSetResolver()
+ {
+ $resolver = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderResolverInterface')->getMock();
+
+ $loader = new ProjectLoader1();
+ $loader->setResolver($resolver);
+
+ $this->assertSame($resolver, $loader->getResolver(), '->setResolver() sets the resolver loader');
+ }
+
+ public function testResolve()
+ {
+ $resolvedLoader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock();
+
+ $resolver = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderResolverInterface')->getMock();
+ $resolver->expects($this->once())
+ ->method('resolve')
+ ->with('foo.xml')
+ ->will($this->returnValue($resolvedLoader));
+
+ $loader = new ProjectLoader1();
+ $loader->setResolver($resolver);
+
+ $this->assertSame($loader, $loader->resolve('foo.foo'), '->resolve() finds a loader');
+ $this->assertSame($resolvedLoader, $loader->resolve('foo.xml'), '->resolve() finds a loader');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Exception\FileLoaderLoadException
+ */
+ public function testResolveWhenResolverCannotFindLoader()
+ {
+ $resolver = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderResolverInterface')->getMock();
+ $resolver->expects($this->once())
+ ->method('resolve')
+ ->with('FOOBAR')
+ ->will($this->returnValue(false));
+
+ $loader = new ProjectLoader1();
+ $loader->setResolver($resolver);
+
+ $loader->resolve('FOOBAR');
+ }
+
+ public function testImport()
+ {
+ $resolvedLoader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock();
+ $resolvedLoader->expects($this->once())
+ ->method('load')
+ ->with('foo')
+ ->will($this->returnValue('yes'));
+
+ $resolver = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderResolverInterface')->getMock();
+ $resolver->expects($this->once())
+ ->method('resolve')
+ ->with('foo')
+ ->will($this->returnValue($resolvedLoader));
+
+ $loader = new ProjectLoader1();
+ $loader->setResolver($resolver);
+
+ $this->assertEquals('yes', $loader->import('foo'));
+ }
+
+ public function testImportWithType()
+ {
+ $resolvedLoader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock();
+ $resolvedLoader->expects($this->once())
+ ->method('load')
+ ->with('foo', 'bar')
+ ->will($this->returnValue('yes'));
+
+ $resolver = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderResolverInterface')->getMock();
+ $resolver->expects($this->once())
+ ->method('resolve')
+ ->with('foo', 'bar')
+ ->will($this->returnValue($resolvedLoader));
+
+ $loader = new ProjectLoader1();
+ $loader->setResolver($resolver);
+
+ $this->assertEquals('yes', $loader->import('foo', 'bar'));
+ }
+}
+
+class ProjectLoader1 extends Loader
+{
+ public function load($resource, $type = null)
+ {
+ }
+
+ public function supports($resource, $type = null)
+ {
+ return is_string($resource) && 'foo' === pathinfo($resource, PATHINFO_EXTENSION);
+ }
+
+ public function getType()
+ {
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Resource/ClassExistenceResourceTest.php b/console/skel/symfony/config/Tests/Resource/ClassExistenceResourceTest.php
new file mode 100644
index 0000000..3daef17
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Resource/ClassExistenceResourceTest.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Resource;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\ClassExistenceResource;
+use Symfony\Component\Config\Tests\Fixtures\Resource\ConditionalClass;
+
+class ClassExistenceResourceTest extends TestCase
+{
+ public function testToString()
+ {
+ $res = new ClassExistenceResource('BarClass');
+ $this->assertSame('BarClass', (string) $res);
+ }
+
+ public function testGetResource()
+ {
+ $res = new ClassExistenceResource('BarClass');
+ $this->assertSame('BarClass', $res->getResource());
+ }
+
+ public function testIsFreshWhenClassDoesNotExist()
+ {
+ $res = new ClassExistenceResource('Symfony\Component\Config\Tests\Fixtures\BarClass');
+
+ $this->assertTrue($res->isFresh(time()));
+
+ eval(<<assertFalse($res->isFresh(time()));
+ }
+
+ public function testIsFreshWhenClassExists()
+ {
+ $res = new ClassExistenceResource('Symfony\Component\Config\Tests\Resource\ClassExistenceResourceTest');
+
+ $this->assertTrue($res->isFresh(time()));
+ }
+
+ public function testExistsKo()
+ {
+ spl_autoload_register($autoloader = function ($class) use (&$loadedClass) { $loadedClass = $class; });
+
+ try {
+ $res = new ClassExistenceResource('MissingFooClass');
+ $this->assertTrue($res->isFresh(0));
+
+ $this->assertSame('MissingFooClass', $loadedClass);
+
+ $loadedClass = 123;
+
+ $res = new ClassExistenceResource('MissingFooClass', false);
+
+ $this->assertSame(123, $loadedClass);
+ } finally {
+ spl_autoload_unregister($autoloader);
+ }
+ }
+
+ public function testConditionalClass()
+ {
+ $res = new ClassExistenceResource(ConditionalClass::class, false);
+
+ $this->assertFalse($res->isFresh(0));
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Resource/ComposerResourceTest.php b/console/skel/symfony/config/Tests/Resource/ComposerResourceTest.php
new file mode 100644
index 0000000..6857c76
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Resource/ComposerResourceTest.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Resource;
+
+use Composer\Autoload\ClassLoader;
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\ComposerResource;
+
+class ComposerResourceTest extends TestCase
+{
+ public function testGetVendor()
+ {
+ $res = new ComposerResource();
+
+ $r = new \ReflectionClass(ClassLoader::class);
+ $found = false;
+
+ foreach ($res->getVendors() as $vendor) {
+ if ($vendor && 0 === strpos($r->getFileName(), $vendor)) {
+ $found = true;
+ break;
+ }
+ }
+
+ $this->assertTrue($found);
+ }
+
+ public function testSerializeUnserialize()
+ {
+ $res = new ComposerResource();
+ $ser = unserialize(serialize($res));
+
+ $this->assertTrue($res->isFresh(0));
+ $this->assertTrue($ser->isFresh(0));
+
+ $this->assertEquals($res, $ser);
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Resource/DirectoryResourceTest.php b/console/skel/symfony/config/Tests/Resource/DirectoryResourceTest.php
new file mode 100644
index 0000000..99c75f1
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Resource/DirectoryResourceTest.php
@@ -0,0 +1,183 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Resource;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\DirectoryResource;
+
+class DirectoryResourceTest extends TestCase
+{
+ protected $directory;
+
+ protected function setUp()
+ {
+ $this->directory = sys_get_temp_dir().DIRECTORY_SEPARATOR.'symfonyDirectoryIterator';
+ if (!file_exists($this->directory)) {
+ mkdir($this->directory);
+ }
+ touch($this->directory.'/tmp.xml');
+ }
+
+ protected function tearDown()
+ {
+ if (!is_dir($this->directory)) {
+ return;
+ }
+ $this->removeDirectory($this->directory);
+ }
+
+ protected function removeDirectory($directory)
+ {
+ $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory), \RecursiveIteratorIterator::CHILD_FIRST);
+ foreach ($iterator as $path) {
+ if (preg_match('#[/\\\\]\.\.?$#', $path->__toString())) {
+ continue;
+ }
+ if ($path->isDir()) {
+ rmdir($path->__toString());
+ } else {
+ unlink($path->__toString());
+ }
+ }
+ rmdir($directory);
+ }
+
+ public function testGetResource()
+ {
+ $resource = new DirectoryResource($this->directory);
+ $this->assertSame(realpath($this->directory), $resource->getResource(), '->getResource() returns the path to the resource');
+ }
+
+ public function testGetPattern()
+ {
+ $resource = new DirectoryResource($this->directory, 'bar');
+ $this->assertEquals('bar', $resource->getPattern());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessageRegExp /The directory ".*" does not exist./
+ */
+ public function testResourceDoesNotExist()
+ {
+ $resource = new DirectoryResource('/____foo/foobar'.mt_rand(1, 999999));
+ }
+
+ public function testIsFresh()
+ {
+ $resource = new DirectoryResource($this->directory);
+ $this->assertTrue($resource->isFresh(time() + 10), '->isFresh() returns true if the resource has not changed');
+ $this->assertFalse($resource->isFresh(time() - 86400), '->isFresh() returns false if the resource has been updated');
+ }
+
+ public function testIsFreshForDeletedResources()
+ {
+ $resource = new DirectoryResource($this->directory);
+ $this->removeDirectory($this->directory);
+
+ $this->assertFalse($resource->isFresh(time()), '->isFresh() returns false if the resource does not exist');
+ }
+
+ public function testIsFreshUpdateFile()
+ {
+ $resource = new DirectoryResource($this->directory);
+ touch($this->directory.'/tmp.xml', time() + 20);
+ $this->assertFalse($resource->isFresh(time() + 10), '->isFresh() returns false if an existing file is modified');
+ }
+
+ public function testIsFreshNewFile()
+ {
+ $resource = new DirectoryResource($this->directory);
+ touch($this->directory.'/new.xml', time() + 20);
+ $this->assertFalse($resource->isFresh(time() + 10), '->isFresh() returns false if a new file is added');
+ }
+
+ public function testIsFreshNewFileWithDifferentPattern()
+ {
+ $resource = new DirectoryResource($this->directory, '/.xml$/');
+ touch($this->directory.'/new.yaml', time() + 20);
+ $this->assertTrue($resource->isFresh(time() + 10), '->isFresh() returns true if a new file with a non-matching pattern is added');
+ }
+
+ public function testIsFreshDeleteFile()
+ {
+ $resource = new DirectoryResource($this->directory);
+ $time = time();
+ sleep(1);
+ unlink($this->directory.'/tmp.xml');
+ $this->assertFalse($resource->isFresh($time), '->isFresh() returns false if an existing file is removed');
+ }
+
+ public function testIsFreshDeleteDirectory()
+ {
+ $resource = new DirectoryResource($this->directory);
+ $this->removeDirectory($this->directory);
+ $this->assertFalse($resource->isFresh(time()), '->isFresh() returns false if the whole resource is removed');
+ }
+
+ public function testIsFreshCreateFileInSubdirectory()
+ {
+ $subdirectory = $this->directory.'/subdirectory';
+ mkdir($subdirectory);
+
+ $resource = new DirectoryResource($this->directory);
+ $this->assertTrue($resource->isFresh(time() + 10), '->isFresh() returns true if an unmodified subdirectory exists');
+
+ touch($subdirectory.'/newfile.xml', time() + 20);
+ $this->assertFalse($resource->isFresh(time() + 10), '->isFresh() returns false if a new file in a subdirectory is added');
+ }
+
+ public function testIsFreshModifySubdirectory()
+ {
+ $resource = new DirectoryResource($this->directory);
+
+ $subdirectory = $this->directory.'/subdirectory';
+ mkdir($subdirectory);
+ touch($subdirectory, time() + 20);
+
+ $this->assertFalse($resource->isFresh(time() + 10), '->isFresh() returns false if a subdirectory is modified (e.g. a file gets deleted)');
+ }
+
+ public function testFilterRegexListNoMatch()
+ {
+ $resource = new DirectoryResource($this->directory, '/\.(foo|xml)$/');
+
+ touch($this->directory.'/new.bar', time() + 20);
+ $this->assertTrue($resource->isFresh(time() + 10), '->isFresh() returns true if a new file not matching the filter regex is created');
+ }
+
+ public function testFilterRegexListMatch()
+ {
+ $resource = new DirectoryResource($this->directory, '/\.(foo|xml)$/');
+
+ touch($this->directory.'/new.xml', time() + 20);
+ $this->assertFalse($resource->isFresh(time() + 10), '->isFresh() returns false if an new file matching the filter regex is created ');
+ }
+
+ public function testSerializeUnserialize()
+ {
+ $resource = new DirectoryResource($this->directory, '/\.(foo|xml)$/');
+
+ $unserialized = unserialize(serialize($resource));
+
+ $this->assertSame(realpath($this->directory), $resource->getResource());
+ $this->assertSame('/\.(foo|xml)$/', $resource->getPattern());
+ }
+
+ public function testResourcesWithDifferentPatternsAreDifferent()
+ {
+ $resourceA = new DirectoryResource($this->directory, '/.xml$/');
+ $resourceB = new DirectoryResource($this->directory, '/.yaml$/');
+
+ $this->assertEquals(2, count(array_unique(array($resourceA, $resourceB))));
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Resource/FileExistenceResourceTest.php b/console/skel/symfony/config/Tests/Resource/FileExistenceResourceTest.php
new file mode 100644
index 0000000..433f65e
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Resource/FileExistenceResourceTest.php
@@ -0,0 +1,71 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Resource;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\FileExistenceResource;
+
+class FileExistenceResourceTest extends TestCase
+{
+ protected $resource;
+ protected $file;
+ protected $time;
+
+ protected function setUp()
+ {
+ $this->file = realpath(sys_get_temp_dir()).'/tmp.xml';
+ $this->time = time();
+ $this->resource = new FileExistenceResource($this->file);
+ }
+
+ protected function tearDown()
+ {
+ if (file_exists($this->file)) {
+ unlink($this->file);
+ }
+ }
+
+ public function testToString()
+ {
+ $this->assertSame($this->file, (string) $this->resource);
+ }
+
+ public function testGetResource()
+ {
+ $this->assertSame($this->file, $this->resource->getResource(), '->getResource() returns the path to the resource');
+ }
+
+ public function testIsFreshWithExistingResource()
+ {
+ touch($this->file, $this->time);
+ $serialized = serialize(new FileExistenceResource($this->file));
+
+ $resource = unserialize($serialized);
+ $this->assertTrue($resource->isFresh($this->time), '->isFresh() returns true if the resource is still present');
+
+ unlink($this->file);
+ $resource = unserialize($serialized);
+ $this->assertFalse($resource->isFresh($this->time), '->isFresh() returns false if the resource has been deleted');
+ }
+
+ public function testIsFreshWithAbsentResource()
+ {
+ $serialized = serialize(new FileExistenceResource($this->file));
+
+ $resource = unserialize($serialized);
+ $this->assertTrue($resource->isFresh($this->time), '->isFresh() returns true if the resource is still absent');
+
+ touch($this->file, $this->time);
+ $resource = unserialize($serialized);
+ $this->assertFalse($resource->isFresh($this->time), '->isFresh() returns false if the resource has been created');
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Resource/FileResourceTest.php b/console/skel/symfony/config/Tests/Resource/FileResourceTest.php
new file mode 100644
index 0000000..97781ff
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Resource/FileResourceTest.php
@@ -0,0 +1,85 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Resource;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\FileResource;
+
+class FileResourceTest extends TestCase
+{
+ protected $resource;
+ protected $file;
+ protected $time;
+
+ protected function setUp()
+ {
+ $this->file = sys_get_temp_dir().'/tmp.xml';
+ $this->time = time();
+ touch($this->file, $this->time);
+ $this->resource = new FileResource($this->file);
+ }
+
+ protected function tearDown()
+ {
+ if (!file_exists($this->file)) {
+ return;
+ }
+
+ unlink($this->file);
+ }
+
+ public function testGetResource()
+ {
+ $this->assertSame(realpath($this->file), $this->resource->getResource(), '->getResource() returns the path to the resource');
+ }
+
+ public function testGetResourceWithScheme()
+ {
+ $resource = new FileResource('file://'.$this->file);
+ $this->assertSame('file://'.$this->file, $resource->getResource(), '->getResource() returns the path to the schemed resource');
+ }
+
+ public function testToString()
+ {
+ $this->assertSame(realpath($this->file), (string) $this->resource);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessageRegExp /The file ".*" does not exist./
+ */
+ public function testResourceDoesNotExist()
+ {
+ $resource = new FileResource('/____foo/foobar'.mt_rand(1, 999999));
+ }
+
+ public function testIsFresh()
+ {
+ $this->assertTrue($this->resource->isFresh($this->time), '->isFresh() returns true if the resource has not changed in same second');
+ $this->assertTrue($this->resource->isFresh($this->time + 10), '->isFresh() returns true if the resource has not changed');
+ $this->assertFalse($this->resource->isFresh($this->time - 86400), '->isFresh() returns false if the resource has been updated');
+ }
+
+ public function testIsFreshForDeletedResources()
+ {
+ unlink($this->file);
+
+ $this->assertFalse($this->resource->isFresh($this->time), '->isFresh() returns false if the resource does not exist');
+ }
+
+ public function testSerializeUnserialize()
+ {
+ $unserialized = unserialize(serialize($this->resource));
+
+ $this->assertSame(realpath($this->file), $this->resource->getResource());
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Resource/GlobResourceTest.php b/console/skel/symfony/config/Tests/Resource/GlobResourceTest.php
new file mode 100644
index 0000000..b84cc9d
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Resource/GlobResourceTest.php
@@ -0,0 +1,105 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Resource;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\GlobResource;
+
+class GlobResourceTest extends TestCase
+{
+ protected function tearDown()
+ {
+ $dir = dirname(__DIR__).'/Fixtures';
+ @rmdir($dir.'/TmpGlob');
+ @unlink($dir.'/TmpGlob');
+ @unlink($dir.'/Resource/TmpGlob');
+ touch($dir.'/Resource/.hiddenFile');
+ }
+
+ public function testIterator()
+ {
+ $dir = dirname(__DIR__).DIRECTORY_SEPARATOR.'Fixtures';
+ $resource = new GlobResource($dir, '/Resource', true);
+
+ $paths = iterator_to_array($resource);
+
+ $file = $dir.'/Resource'.DIRECTORY_SEPARATOR.'ConditionalClass.php';
+ $this->assertEquals(array($file => new \SplFileInfo($file)), $paths);
+ $this->assertInstanceOf('SplFileInfo', current($paths));
+ $this->assertSame($dir, $resource->getPrefix());
+ }
+
+ public function testIsFreshNonRecursiveDetectsNewFile()
+ {
+ $dir = dirname(__DIR__).'/Fixtures';
+ $resource = new GlobResource($dir, '/*', false);
+
+ $this->assertTrue($resource->isFresh(0));
+
+ mkdir($dir.'/TmpGlob');
+ $this->assertTrue($resource->isFresh(0));
+
+ rmdir($dir.'/TmpGlob');
+ $this->assertTrue($resource->isFresh(0));
+
+ touch($dir.'/TmpGlob');
+ $this->assertFalse($resource->isFresh(0));
+
+ unlink($dir.'/TmpGlob');
+ $this->assertTrue($resource->isFresh(0));
+ }
+
+ public function testIsFreshNonRecursiveDetectsRemovedFile()
+ {
+ $dir = dirname(__DIR__).'/Fixtures';
+ $resource = new GlobResource($dir, '/*', false);
+
+ touch($dir.'/TmpGlob');
+ touch($dir.'/.TmpGlob');
+ $this->assertTrue($resource->isFresh(0));
+
+ unlink($dir.'/.TmpGlob');
+ $this->assertTrue($resource->isFresh(0));
+
+ unlink($dir.'/TmpGlob');
+ $this->assertFalse($resource->isFresh(0));
+ }
+
+ public function testIsFreshRecursiveDetectsRemovedFile()
+ {
+ $dir = dirname(__DIR__).'/Fixtures';
+ $resource = new GlobResource($dir, '/*', true);
+
+ touch($dir.'/Resource/TmpGlob');
+ $this->assertTrue($resource->isFresh(0));
+
+ unlink($dir.'/Resource/TmpGlob');
+ $this->assertFalse($resource->isFresh(0));
+
+ touch($dir.'/Resource/TmpGlob');
+ $this->assertTrue($resource->isFresh(0));
+
+ unlink($dir.'/Resource/.hiddenFile');
+ $this->assertTrue($resource->isFresh(0));
+ }
+
+ public function testIsFreshRecursiveDetectsNewFile()
+ {
+ $dir = dirname(__DIR__).'/Fixtures';
+ $resource = new GlobResource($dir, '/*', true);
+
+ $this->assertTrue($resource->isFresh(0));
+
+ touch($dir.'/Resource/TmpGlob');
+ $this->assertFalse($resource->isFresh(0));
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Resource/ReflectionClassResourceTest.php b/console/skel/symfony/config/Tests/Resource/ReflectionClassResourceTest.php
new file mode 100644
index 0000000..4eca3d5
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Resource/ReflectionClassResourceTest.php
@@ -0,0 +1,143 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Resource;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\ReflectionClassResource;
+
+class ReflectionClassResourceTest extends TestCase
+{
+ public function testToString()
+ {
+ $res = new ReflectionClassResource(new \ReflectionClass('ErrorException'));
+
+ $this->assertSame('reflection.ErrorException', (string) $res);
+ }
+
+ public function testSerializeUnserialize()
+ {
+ $res = new ReflectionClassResource(new \ReflectionClass(DummyInterface::class));
+ $ser = unserialize(serialize($res));
+
+ $this->assertTrue($res->isFresh(0));
+ $this->assertTrue($ser->isFresh(0));
+
+ $this->assertSame((string) $res, (string) $ser);
+ }
+
+ public function testIsFresh()
+ {
+ $res = new ReflectionClassResource(new \ReflectionClass(__CLASS__));
+ $mtime = filemtime(__FILE__);
+
+ $this->assertTrue($res->isFresh($mtime), '->isFresh() returns true if the resource has not changed in same second');
+ $this->assertTrue($res->isFresh($mtime + 10), '->isFresh() returns true if the resource has not changed');
+ $this->assertTrue($res->isFresh($mtime - 86400), '->isFresh() returns true if the resource has not changed');
+ }
+
+ public function testIsFreshForDeletedResources()
+ {
+ $now = time();
+ $tmp = sys_get_temp_dir().'/tmp.php';
+ file_put_contents($tmp, 'assertTrue($res->isFresh($now));
+
+ unlink($tmp);
+ $this->assertFalse($res->isFresh($now), '->isFresh() returns false if the resource does not exist');
+ }
+
+ /**
+ * @dataProvider provideHashedSignature
+ */
+ public function testHashedSignature($changeExpected, $changedLine, $changedCode)
+ {
+ $code = <<<'EOPHP'
+/* 0*/
+/* 1*/ class %s extends ErrorException
+/* 2*/ {
+/* 3*/ const FOO = 123;
+/* 4*/
+/* 5*/ public $pub = array();
+/* 6*/
+/* 7*/ protected $prot;
+/* 8*/
+/* 9*/ private $priv;
+/*10*/
+/*11*/ public function pub($arg = null) {}
+/*12*/
+/*13*/ protected function prot($a = array()) {}
+/*14*/
+/*15*/ private function priv() {}
+/*16*/ }
+EOPHP;
+
+ static $expectedSignature, $generateSignature;
+
+ if (null === $expectedSignature) {
+ eval(sprintf($code, $class = 'Foo'.str_replace('.', '_', uniqid('', true))));
+ $r = new \ReflectionClass(ReflectionClassResource::class);
+ $generateSignature = $r->getMethod('generateSignature');
+ $generateSignature->setAccessible(true);
+ $generateSignature = $generateSignature->getClosure($r->newInstanceWithoutConstructor());
+ $expectedSignature = implode("\n", iterator_to_array($generateSignature(new \ReflectionClass($class))));
+ }
+
+ $code = explode("\n", $code);
+ $code[$changedLine] = $changedCode;
+ eval(sprintf(implode("\n", $code), $class = 'Foo'.str_replace('.', '_', uniqid('', true))));
+ $signature = implode("\n", iterator_to_array($generateSignature(new \ReflectionClass($class))));
+
+ if ($changeExpected) {
+ $this->assertTrue($expectedSignature !== $signature);
+ } else {
+ $this->assertSame($expectedSignature, $signature);
+ }
+ }
+
+ public function provideHashedSignature()
+ {
+ yield array(0, 0, "// line change\n\n");
+ yield array(1, 0, '/** class docblock */');
+ yield array(1, 1, 'abstract class %s');
+ yield array(1, 1, 'final class %s');
+ yield array(1, 1, 'class %s extends Exception');
+ yield array(1, 1, 'class %s implements '.DummyInterface::class);
+ yield array(1, 3, 'const FOO = 456;');
+ yield array(1, 3, 'const BAR = 123;');
+ yield array(1, 4, '/** pub docblock */');
+ yield array(1, 5, 'protected $pub = array();');
+ yield array(1, 5, 'public $pub = array(123);');
+ yield array(1, 6, '/** prot docblock */');
+ yield array(1, 7, 'private $prot;');
+ yield array(0, 8, '/** priv docblock */');
+ yield array(0, 9, 'private $priv = 123;');
+ yield array(1, 10, '/** pub docblock */');
+ if (\PHP_VERSION_ID >= 50600) {
+ yield array(1, 11, 'public function pub(...$arg) {}');
+ }
+ if (\PHP_VERSION_ID >= 70000) {
+ yield array(1, 11, 'public function pub($arg = null): Foo {}');
+ }
+ yield array(0, 11, "public function pub(\$arg = null) {\nreturn 123;\n}");
+ yield array(1, 12, '/** prot docblock */');
+ yield array(1, 13, 'protected function prot($a = array(123)) {}');
+ yield array(0, 14, '/** priv docblock */');
+ yield array(0, 15, '');
+ }
+}
+
+interface DummyInterface
+{
+}
diff --git a/console/skel/symfony/config/Tests/Resource/ResourceStub.php b/console/skel/symfony/config/Tests/Resource/ResourceStub.php
new file mode 100644
index 0000000..b01729c
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Resource/ResourceStub.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Resource;
+
+use Symfony\Component\Config\Resource\SelfCheckingResourceInterface;
+
+class ResourceStub implements SelfCheckingResourceInterface
+{
+ private $fresh = true;
+
+ public function setFresh($isFresh)
+ {
+ $this->fresh = $isFresh;
+ }
+
+ public function __toString()
+ {
+ return 'stub';
+ }
+
+ public function isFresh($timestamp)
+ {
+ return $this->fresh;
+ }
+}
diff --git a/console/skel/symfony/config/Tests/ResourceCheckerConfigCacheTest.php b/console/skel/symfony/config/Tests/ResourceCheckerConfigCacheTest.php
new file mode 100644
index 0000000..d39742a
--- /dev/null
+++ b/console/skel/symfony/config/Tests/ResourceCheckerConfigCacheTest.php
@@ -0,0 +1,144 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Tests\Resource\ResourceStub;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Config\ResourceCheckerConfigCache;
+
+class ResourceCheckerConfigCacheTest extends TestCase
+{
+ private $cacheFile = null;
+
+ protected function setUp()
+ {
+ $this->cacheFile = tempnam(sys_get_temp_dir(), 'config_');
+ }
+
+ protected function tearDown()
+ {
+ $files = array($this->cacheFile, "{$this->cacheFile}.meta");
+
+ foreach ($files as $file) {
+ if (file_exists($file)) {
+ unlink($file);
+ }
+ }
+ }
+
+ public function testGetPath()
+ {
+ $cache = new ResourceCheckerConfigCache($this->cacheFile);
+
+ $this->assertSame($this->cacheFile, $cache->getPath());
+ }
+
+ public function testCacheIsNotFreshIfEmpty()
+ {
+ $checker = $this->getMockBuilder('\Symfony\Component\Config\ResourceCheckerInterface')->getMock()
+ ->expects($this->never())->method('supports');
+
+ /* If there is nothing in the cache, it needs to be filled (and thus it's not fresh).
+ It does not matter if you provide checkers or not. */
+
+ unlink($this->cacheFile); // remove tempnam() side effect
+ $cache = new ResourceCheckerConfigCache($this->cacheFile, array($checker));
+
+ $this->assertFalse($cache->isFresh());
+ }
+
+ public function testCacheIsFreshIfNocheckerProvided()
+ {
+ /* For example in prod mode, you may choose not to run any checkers
+ at all. In that case, the cache should always be considered fresh. */
+ $cache = new ResourceCheckerConfigCache($this->cacheFile);
+ $this->assertTrue($cache->isFresh());
+ }
+
+ public function testResourcesWithoutcheckersAreIgnoredAndConsideredFresh()
+ {
+ /* As in the previous test, but this time we have a resource. */
+ $cache = new ResourceCheckerConfigCache($this->cacheFile);
+ $cache->write('', array(new ResourceStub()));
+
+ $this->assertTrue($cache->isFresh()); // no (matching) ResourceChecker passed
+ }
+
+ public function testIsFreshWithchecker()
+ {
+ $checker = $this->getMockBuilder('\Symfony\Component\Config\ResourceCheckerInterface')->getMock();
+
+ $checker->expects($this->once())
+ ->method('supports')
+ ->willReturn(true);
+
+ $checker->expects($this->once())
+ ->method('isFresh')
+ ->willReturn(true);
+
+ $cache = new ResourceCheckerConfigCache($this->cacheFile, array($checker));
+ $cache->write('', array(new ResourceStub()));
+
+ $this->assertTrue($cache->isFresh());
+ }
+
+ public function testIsNotFreshWithchecker()
+ {
+ $checker = $this->getMockBuilder('\Symfony\Component\Config\ResourceCheckerInterface')->getMock();
+
+ $checker->expects($this->once())
+ ->method('supports')
+ ->willReturn(true);
+
+ $checker->expects($this->once())
+ ->method('isFresh')
+ ->willReturn(false);
+
+ $cache = new ResourceCheckerConfigCache($this->cacheFile, array($checker));
+ $cache->write('', array(new ResourceStub()));
+
+ $this->assertFalse($cache->isFresh());
+ }
+
+ public function testCacheIsNotFreshWhenUnserializeFails()
+ {
+ $checker = $this->getMockBuilder('\Symfony\Component\Config\ResourceCheckerInterface')->getMock();
+ $cache = new ResourceCheckerConfigCache($this->cacheFile, array($checker));
+ $cache->write('foo', array(new FileResource(__FILE__)));
+
+ $metaFile = "{$this->cacheFile}.meta";
+ file_put_contents($metaFile, str_replace('FileResource', 'ClassNotHere', file_get_contents($metaFile)));
+
+ $this->assertFalse($cache->isFresh());
+ }
+
+ public function testCacheKeepsContent()
+ {
+ $cache = new ResourceCheckerConfigCache($this->cacheFile);
+ $cache->write('FOOBAR');
+
+ $this->assertSame('FOOBAR', file_get_contents($cache->getPath()));
+ }
+
+ public function testCacheIsNotFreshIfNotExistsMetaFile()
+ {
+ $checker = $this->getMockBuilder('\Symfony\Component\Config\ResourceCheckerInterface')->getMock();
+ $cache = new ResourceCheckerConfigCache($this->cacheFile, array($checker));
+ $cache->write('foo', array(new FileResource(__FILE__)));
+
+ $metaFile = "{$this->cacheFile}.meta";
+ unlink($metaFile);
+
+ $this->assertFalse($cache->isFresh());
+ }
+}
diff --git a/console/skel/symfony/config/Tests/Util/XmlUtilsTest.php b/console/skel/symfony/config/Tests/Util/XmlUtilsTest.php
new file mode 100644
index 0000000..161dc61
--- /dev/null
+++ b/console/skel/symfony/config/Tests/Util/XmlUtilsTest.php
@@ -0,0 +1,203 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Util;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Util\XmlUtils;
+
+class XmlUtilsTest extends TestCase
+{
+ public function testLoadFile()
+ {
+ $fixtures = __DIR__.'/../Fixtures/Util/';
+
+ try {
+ XmlUtils::loadFile($fixtures.'invalid.xml');
+ $this->fail();
+ } catch (\InvalidArgumentException $e) {
+ $this->assertContains('ERROR 77', $e->getMessage());
+ }
+
+ try {
+ XmlUtils::loadFile($fixtures.'document_type.xml');
+ $this->fail();
+ } catch (\InvalidArgumentException $e) {
+ $this->assertContains('Document types are not allowed', $e->getMessage());
+ }
+
+ try {
+ XmlUtils::loadFile($fixtures.'invalid_schema.xml', $fixtures.'schema.xsd');
+ $this->fail();
+ } catch (\InvalidArgumentException $e) {
+ $this->assertContains('ERROR 1845', $e->getMessage());
+ }
+
+ try {
+ XmlUtils::loadFile($fixtures.'invalid_schema.xml', 'invalid_callback_or_file');
+ $this->fail();
+ } catch (\InvalidArgumentException $e) {
+ $this->assertContains('XSD file or callable', $e->getMessage());
+ }
+
+ $mock = $this->getMockBuilder(__NAMESPACE__.'\Validator')->getMock();
+ $mock->expects($this->exactly(2))->method('validate')->will($this->onConsecutiveCalls(false, true));
+
+ try {
+ XmlUtils::loadFile($fixtures.'valid.xml', array($mock, 'validate'));
+ $this->fail();
+ } catch (\InvalidArgumentException $e) {
+ $this->assertContains('is not valid', $e->getMessage());
+ }
+
+ $this->assertInstanceOf('DOMDocument', XmlUtils::loadFile($fixtures.'valid.xml', array($mock, 'validate')));
+ $this->assertSame(array(), libxml_get_errors());
+ }
+
+ public function testLoadFileWithInternalErrorsEnabled()
+ {
+ $internalErrors = libxml_use_internal_errors(true);
+
+ $this->assertSame(array(), libxml_get_errors());
+ $this->assertInstanceOf('DOMDocument', XmlUtils::loadFile(__DIR__.'/../Fixtures/Util/invalid_schema.xml'));
+ $this->assertSame(array(), libxml_get_errors());
+
+ libxml_clear_errors();
+ libxml_use_internal_errors($internalErrors);
+ }
+
+ /**
+ * @dataProvider getDataForConvertDomToArray
+ */
+ public function testConvertDomToArray($expected, $xml, $root = false, $checkPrefix = true)
+ {
+ $dom = new \DOMDocument();
+ $dom->loadXML($root ? $xml : ''.$xml.' ');
+
+ $this->assertSame($expected, XmlUtils::convertDomElementToArray($dom->documentElement, $checkPrefix));
+ }
+
+ public function getDataForConvertDomToArray()
+ {
+ return array(
+ array(null, ''),
+ array('bar', 'bar'),
+ array(array('bar' => 'foobar'), ' ', true),
+ array(array('foo' => null), ' '),
+ array(array('foo' => 'bar'), 'bar '),
+ array(array('foo' => array('foo' => 'bar')), ' '),
+ array(array('foo' => array('foo' => 0)), '0 '),
+ array(array('foo' => array('foo' => 'bar')), 'bar '),
+ array(array('foo' => array('foo' => 'bar', 'value' => 'text')), 'text '),
+ array(array('foo' => array('attr' => 'bar', 'foo' => 'text')), 'text '),
+ array(array('foo' => array('bar', 'text')), 'bar text '),
+ array(array('foo' => array(array('foo' => 'bar'), array('foo' => 'text'))), ' '),
+ array(array('foo' => array('foo' => array('bar', 'text'))), 'text '),
+ array(array('foo' => 'bar'), 'bar '),
+ array(array('foo' => 'text'), 'text '),
+ array(array('foo' => array('bar' => 'bar', 'value' => 'text')), 'text ', false, false),
+ array(array('attr' => 1, 'b' => 'hello'), 'hello 2 ', true),
+ );
+ }
+
+ /**
+ * @dataProvider getDataForPhpize
+ */
+ public function testPhpize($expected, $value)
+ {
+ $this->assertSame($expected, XmlUtils::phpize($value));
+ }
+
+ public function getDataForPhpize()
+ {
+ return array(
+ array('', ''),
+ array(null, 'null'),
+ array(true, 'true'),
+ array(false, 'false'),
+ array(null, 'Null'),
+ array(true, 'True'),
+ array(false, 'False'),
+ array(0, '0'),
+ array(1, '1'),
+ array(-1, '-1'),
+ array(0777, '0777'),
+ array(255, '0xFF'),
+ array(100.0, '1e2'),
+ array(-120.0, '-1.2E2'),
+ array(-10100.1, '-10100.1'),
+ array('-10,100.1', '-10,100.1'),
+ array('1234 5678 9101 1121 3141', '1234 5678 9101 1121 3141'),
+ array('1,2,3,4', '1,2,3,4'),
+ array('11,22,33,44', '11,22,33,44'),
+ array('11,222,333,4', '11,222,333,4'),
+ array('1,222,333,444', '1,222,333,444'),
+ array('11,222,333,444', '11,222,333,444'),
+ array('111,222,333,444', '111,222,333,444'),
+ array('1111,2222,3333,4444,5555', '1111,2222,3333,4444,5555'),
+ array('foo', 'foo'),
+ array(6, '0b0110'),
+ );
+ }
+
+ public function testLoadEmptyXmlFile()
+ {
+ $file = __DIR__.'/../Fixtures/foo.xml';
+
+ if (method_exists($this, 'expectException')) {
+ $this->expectException('InvalidArgumentException');
+ $this->expectExceptionMessage(sprintf('File %s does not contain valid XML, it is empty.', $file));
+ } else {
+ $this->setExpectedException('InvalidArgumentException', sprintf('File %s does not contain valid XML, it is empty.', $file));
+ }
+
+ XmlUtils::loadFile($file);
+ }
+
+ // test for issue https://github.com/symfony/symfony/issues/9731
+ public function testLoadWrongEmptyXMLWithErrorHandler()
+ {
+ $originalDisableEntities = libxml_disable_entity_loader(false);
+ $errorReporting = error_reporting(-1);
+
+ set_error_handler(function ($errno, $errstr) {
+ throw new \Exception($errstr, $errno);
+ });
+
+ $file = __DIR__.'/../Fixtures/foo.xml';
+ try {
+ try {
+ XmlUtils::loadFile($file);
+ $this->fail('An exception should have been raised');
+ } catch (\InvalidArgumentException $e) {
+ $this->assertEquals(sprintf('File %s does not contain valid XML, it is empty.', $file), $e->getMessage());
+ }
+ } finally {
+ restore_error_handler();
+ error_reporting($errorReporting);
+ }
+
+ $disableEntities = libxml_disable_entity_loader(true);
+ libxml_disable_entity_loader($disableEntities);
+
+ libxml_disable_entity_loader($originalDisableEntities);
+
+ $this->assertFalse($disableEntities);
+
+ // should not throw an exception
+ XmlUtils::loadFile(__DIR__.'/../Fixtures/Util/valid.xml', __DIR__.'/../Fixtures/Util/schema.xsd');
+ }
+}
+
+interface Validator
+{
+ public function validate();
+}
diff --git a/console/skel/symfony/config/Util/XmlUtils.php b/console/skel/symfony/config/Util/XmlUtils.php
new file mode 100644
index 0000000..25d9b0a
--- /dev/null
+++ b/console/skel/symfony/config/Util/XmlUtils.php
@@ -0,0 +1,238 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Util;
+
+/**
+ * XMLUtils is a bunch of utility methods to XML operations.
+ *
+ * This class contains static methods only and is not meant to be instantiated.
+ *
+ * @author Fabien Potencier
+ * @author Martin Hasoň
+ */
+class XmlUtils
+{
+ /**
+ * This class should not be instantiated.
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Loads an XML file.
+ *
+ * @param string $file An XML file path
+ * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation
+ *
+ * @return \DOMDocument
+ *
+ * @throws \InvalidArgumentException When loading of XML file returns error
+ */
+ public static function loadFile($file, $schemaOrCallable = null)
+ {
+ $content = @file_get_contents($file);
+ if ('' === trim($content)) {
+ throw new \InvalidArgumentException(sprintf('File %s does not contain valid XML, it is empty.', $file));
+ }
+
+ $internalErrors = libxml_use_internal_errors(true);
+ $disableEntities = libxml_disable_entity_loader(true);
+ libxml_clear_errors();
+
+ $dom = new \DOMDocument();
+ $dom->validateOnParse = true;
+ if (!$dom->loadXML($content, LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) {
+ libxml_disable_entity_loader($disableEntities);
+
+ throw new \InvalidArgumentException(implode("\n", static::getXmlErrors($internalErrors)));
+ }
+
+ $dom->normalizeDocument();
+
+ libxml_use_internal_errors($internalErrors);
+ libxml_disable_entity_loader($disableEntities);
+
+ foreach ($dom->childNodes as $child) {
+ if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
+ throw new \InvalidArgumentException('Document types are not allowed.');
+ }
+ }
+
+ if (null !== $schemaOrCallable) {
+ $internalErrors = libxml_use_internal_errors(true);
+ libxml_clear_errors();
+
+ $e = null;
+ if (is_callable($schemaOrCallable)) {
+ try {
+ $valid = call_user_func($schemaOrCallable, $dom, $internalErrors);
+ } catch (\Exception $e) {
+ $valid = false;
+ }
+ } elseif (!is_array($schemaOrCallable) && is_file((string) $schemaOrCallable)) {
+ $schemaSource = file_get_contents((string) $schemaOrCallable);
+ $valid = @$dom->schemaValidateSource($schemaSource);
+ } else {
+ libxml_use_internal_errors($internalErrors);
+
+ throw new \InvalidArgumentException('The schemaOrCallable argument has to be a valid path to XSD file or callable.');
+ }
+
+ if (!$valid) {
+ $messages = static::getXmlErrors($internalErrors);
+ if (empty($messages)) {
+ $messages = array(sprintf('The XML file "%s" is not valid.', $file));
+ }
+ throw new \InvalidArgumentException(implode("\n", $messages), 0, $e);
+ }
+ }
+
+ libxml_clear_errors();
+ libxml_use_internal_errors($internalErrors);
+
+ return $dom;
+ }
+
+ /**
+ * Converts a \DomElement object to a PHP array.
+ *
+ * The following rules applies during the conversion:
+ *
+ * * Each tag is converted to a key value or an array
+ * if there is more than one "value"
+ *
+ * * The content of a tag is set under a "value" key (bar )
+ * if the tag also has some nested tags
+ *
+ * * The attributes are converted to keys ( )
+ *
+ * * The nested-tags are converted to keys (bar )
+ *
+ * @param \DomElement $element A \DomElement instance
+ * @param bool $checkPrefix Check prefix in an element or an attribute name
+ *
+ * @return array A PHP array
+ */
+ public static function convertDomElementToArray(\DOMElement $element, $checkPrefix = true)
+ {
+ $prefix = (string) $element->prefix;
+ $empty = true;
+ $config = array();
+ foreach ($element->attributes as $name => $node) {
+ if ($checkPrefix && !in_array((string) $node->prefix, array('', $prefix), true)) {
+ continue;
+ }
+ $config[$name] = static::phpize($node->value);
+ $empty = false;
+ }
+
+ $nodeValue = false;
+ foreach ($element->childNodes as $node) {
+ if ($node instanceof \DOMText) {
+ if ('' !== trim($node->nodeValue)) {
+ $nodeValue = trim($node->nodeValue);
+ $empty = false;
+ }
+ } elseif ($checkPrefix && $prefix != (string) $node->prefix) {
+ continue;
+ } elseif (!$node instanceof \DOMComment) {
+ $value = static::convertDomElementToArray($node, $checkPrefix);
+
+ $key = $node->localName;
+ if (isset($config[$key])) {
+ if (!is_array($config[$key]) || !is_int(key($config[$key]))) {
+ $config[$key] = array($config[$key]);
+ }
+ $config[$key][] = $value;
+ } else {
+ $config[$key] = $value;
+ }
+
+ $empty = false;
+ }
+ }
+
+ if (false !== $nodeValue) {
+ $value = static::phpize($nodeValue);
+ if (count($config)) {
+ $config['value'] = $value;
+ } else {
+ $config = $value;
+ }
+ }
+
+ return !$empty ? $config : null;
+ }
+
+ /**
+ * Converts an xml value to a PHP type.
+ *
+ * @param mixed $value
+ *
+ * @return mixed
+ */
+ public static function phpize($value)
+ {
+ $value = (string) $value;
+ $lowercaseValue = strtolower($value);
+
+ switch (true) {
+ case 'null' === $lowercaseValue:
+ return;
+ case ctype_digit($value):
+ $raw = $value;
+ $cast = (int) $value;
+
+ return '0' == $value[0] ? octdec($value) : (((string) $raw === (string) $cast) ? $cast : $raw);
+ case isset($value[1]) && '-' === $value[0] && ctype_digit(substr($value, 1)):
+ $raw = $value;
+ $cast = (int) $value;
+
+ return '0' == $value[1] ? octdec($value) : (((string) $raw === (string) $cast) ? $cast : $raw);
+ case 'true' === $lowercaseValue:
+ return true;
+ case 'false' === $lowercaseValue:
+ return false;
+ case isset($value[1]) && '0b' == $value[0].$value[1]:
+ return bindec($value);
+ case is_numeric($value):
+ return '0x' === $value[0].$value[1] ? hexdec($value) : (float) $value;
+ case preg_match('/^0x[0-9a-f]++$/i', $value):
+ return hexdec($value);
+ case preg_match('/^(-|\+)?[0-9]+(\.[0-9]+)?$/', $value):
+ return (float) $value;
+ default:
+ return $value;
+ }
+ }
+
+ protected static function getXmlErrors($internalErrors)
+ {
+ $errors = array();
+ foreach (libxml_get_errors() as $error) {
+ $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
+ LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
+ $error->code,
+ trim($error->message),
+ $error->file ?: 'n/a',
+ $error->line,
+ $error->column
+ );
+ }
+
+ libxml_clear_errors();
+ libxml_use_internal_errors($internalErrors);
+
+ return $errors;
+ }
+}
diff --git a/console/skel/symfony/config/composer.json b/console/skel/symfony/config/composer.json
new file mode 100644
index 0000000..88090d4
--- /dev/null
+++ b/console/skel/symfony/config/composer.json
@@ -0,0 +1,44 @@
+{
+ "name": "symfony/config",
+ "type": "library",
+ "description": "Symfony Config Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.5.9",
+ "symfony/filesystem": "~2.8|~3.0"
+ },
+ "require-dev": {
+ "symfony/yaml": "~3.0",
+ "symfony/dependency-injection": "~3.3"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<3.3"
+ },
+ "suggest": {
+ "symfony/yaml": "To use the yaml reference dumper"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Config\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.3-dev"
+ }
+ }
+}
diff --git a/console/skel/symfony/config/phpunit.xml.dist b/console/skel/symfony/config/phpunit.xml.dist
new file mode 100644
index 0000000..36ef339
--- /dev/null
+++ b/console/skel/symfony/config/phpunit.xml.dist
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Resources
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/console/skel/symfony/expression-language/.gitignore b/console/skel/symfony/expression-language/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/console/skel/symfony/expression-language/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/console/skel/symfony/expression-language/CHANGELOG.md b/console/skel/symfony/expression-language/CHANGELOG.md
new file mode 100644
index 0000000..d00d17c
--- /dev/null
+++ b/console/skel/symfony/expression-language/CHANGELOG.md
@@ -0,0 +1,12 @@
+CHANGELOG
+=========
+
+2.6.0
+-----
+
+ * Added ExpressionFunction and ExpressionFunctionProviderInterface
+
+2.4.0
+-----
+
+ * added the component
diff --git a/console/skel/symfony/expression-language/Compiler.php b/console/skel/symfony/expression-language/Compiler.php
new file mode 100644
index 0000000..282e82d
--- /dev/null
+++ b/console/skel/symfony/expression-language/Compiler.php
@@ -0,0 +1,146 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage;
+
+/**
+ * Compiles a node to PHP code.
+ *
+ * @author Fabien Potencier
+ */
+class Compiler
+{
+ private $source;
+ private $functions;
+
+ public function __construct(array $functions)
+ {
+ $this->functions = $functions;
+ }
+
+ public function getFunction($name)
+ {
+ return $this->functions[$name];
+ }
+
+ /**
+ * Gets the current PHP code after compilation.
+ *
+ * @return string The PHP code
+ */
+ public function getSource()
+ {
+ return $this->source;
+ }
+
+ public function reset()
+ {
+ $this->source = '';
+
+ return $this;
+ }
+
+ /**
+ * Compiles a node.
+ *
+ * @return $this
+ */
+ public function compile(Node\Node $node)
+ {
+ $node->compile($this);
+
+ return $this;
+ }
+
+ public function subcompile(Node\Node $node)
+ {
+ $current = $this->source;
+ $this->source = '';
+
+ $node->compile($this);
+
+ $source = $this->source;
+ $this->source = $current;
+
+ return $source;
+ }
+
+ /**
+ * Adds a raw string to the compiled code.
+ *
+ * @param string $string The string
+ *
+ * @return $this
+ */
+ public function raw($string)
+ {
+ $this->source .= $string;
+
+ return $this;
+ }
+
+ /**
+ * Adds a quoted string to the compiled code.
+ *
+ * @param string $value The string
+ *
+ * @return $this
+ */
+ public function string($value)
+ {
+ $this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\"));
+
+ return $this;
+ }
+
+ /**
+ * Returns a PHP representation of a given value.
+ *
+ * @param mixed $value The value to convert
+ *
+ * @return $this
+ */
+ public function repr($value)
+ {
+ if (\is_int($value) || \is_float($value)) {
+ if (false !== $locale = setlocale(LC_NUMERIC, 0)) {
+ setlocale(LC_NUMERIC, 'C');
+ }
+
+ $this->raw($value);
+
+ if (false !== $locale) {
+ setlocale(LC_NUMERIC, $locale);
+ }
+ } elseif (null === $value) {
+ $this->raw('null');
+ } elseif (\is_bool($value)) {
+ $this->raw($value ? 'true' : 'false');
+ } elseif (\is_array($value)) {
+ $this->raw('[');
+ $first = true;
+ foreach ($value as $key => $value) {
+ if (!$first) {
+ $this->raw(', ');
+ }
+ $first = false;
+ $this->repr($key);
+ $this->raw(' => ');
+ $this->repr($value);
+ }
+ $this->raw(']');
+ } else {
+ $this->string($value);
+ }
+
+ return $this;
+ }
+}
diff --git a/console/skel/symfony/expression-language/Expression.php b/console/skel/symfony/expression-language/Expression.php
new file mode 100644
index 0000000..ac656cc
--- /dev/null
+++ b/console/skel/symfony/expression-language/Expression.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage;
+
+/**
+ * Represents an expression.
+ *
+ * @author Fabien Potencier
+ */
+class Expression
+{
+ protected $expression;
+
+ /**
+ * @param string $expression An expression
+ */
+ public function __construct($expression)
+ {
+ $this->expression = (string) $expression;
+ }
+
+ /**
+ * Gets the expression.
+ *
+ * @return string The expression
+ */
+ public function __toString()
+ {
+ return $this->expression;
+ }
+}
diff --git a/console/skel/symfony/expression-language/ExpressionFunction.php b/console/skel/symfony/expression-language/ExpressionFunction.php
new file mode 100644
index 0000000..77c88f6
--- /dev/null
+++ b/console/skel/symfony/expression-language/ExpressionFunction.php
@@ -0,0 +1,100 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage;
+
+/**
+ * Represents a function that can be used in an expression.
+ *
+ * A function is defined by two PHP callables. The callables are used
+ * by the language to compile and/or evaluate the function.
+ *
+ * The "compiler" function is used at compilation time and must return a
+ * PHP representation of the function call (it receives the function
+ * arguments as arguments).
+ *
+ * The "evaluator" function is used for expression evaluation and must return
+ * the value of the function call based on the values defined for the
+ * expression (it receives the values as a first argument and the function
+ * arguments as remaining arguments).
+ *
+ * @author Fabien Potencier
+ */
+class ExpressionFunction
+{
+ private $name;
+ private $compiler;
+ private $evaluator;
+
+ /**
+ * @param string $name The function name
+ * @param callable $compiler A callable able to compile the function
+ * @param callable $evaluator A callable able to evaluate the function
+ */
+ public function __construct($name, callable $compiler, callable $evaluator)
+ {
+ $this->name = $name;
+ $this->compiler = $compiler;
+ $this->evaluator = $evaluator;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function getCompiler()
+ {
+ return $this->compiler;
+ }
+
+ public function getEvaluator()
+ {
+ return $this->evaluator;
+ }
+
+ /**
+ * Creates an ExpressionFunction from a PHP function name.
+ *
+ * @param string $phpFunctionName The PHP function name
+ * @param string|null $expressionFunctionName The expression function name (default: same than the PHP function name)
+ *
+ * @return self
+ *
+ * @throws \InvalidArgumentException if given PHP function name does not exist
+ * @throws \InvalidArgumentException if given PHP function name is in namespace
+ * and expression function name is not defined
+ */
+ public static function fromPhp($phpFunctionName, $expressionFunctionName = null)
+ {
+ $phpFunctionName = ltrim($phpFunctionName, '\\');
+ if (!\function_exists($phpFunctionName)) {
+ throw new \InvalidArgumentException(sprintf('PHP function "%s" does not exist.', $phpFunctionName));
+ }
+
+ $parts = explode('\\', $phpFunctionName);
+ if (!$expressionFunctionName && \count($parts) > 1) {
+ throw new \InvalidArgumentException(sprintf('An expression function name must be defined when PHP function "%s" is namespaced.', $phpFunctionName));
+ }
+
+ $compiler = function () use ($phpFunctionName) {
+ return sprintf('\%s(%s)', $phpFunctionName, implode(', ', \func_get_args()));
+ };
+
+ $evaluator = function () use ($phpFunctionName) {
+ $args = \func_get_args();
+
+ return \call_user_func_array($phpFunctionName, array_splice($args, 1));
+ };
+
+ return new self($expressionFunctionName ?: end($parts), $compiler, $evaluator);
+ }
+}
diff --git a/console/skel/symfony/expression-language/ExpressionFunctionProviderInterface.php b/console/skel/symfony/expression-language/ExpressionFunctionProviderInterface.php
new file mode 100644
index 0000000..414b013
--- /dev/null
+++ b/console/skel/symfony/expression-language/ExpressionFunctionProviderInterface.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage;
+
+/**
+ * @author Fabien Potencier
+ */
+interface ExpressionFunctionProviderInterface
+{
+ /**
+ * @return ExpressionFunction[] An array of Function instances
+ */
+ public function getFunctions();
+}
diff --git a/console/skel/symfony/expression-language/ExpressionLanguage.php b/console/skel/symfony/expression-language/ExpressionLanguage.php
new file mode 100644
index 0000000..b6760a5
--- /dev/null
+++ b/console/skel/symfony/expression-language/ExpressionLanguage.php
@@ -0,0 +1,178 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage;
+
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheAdapter;
+use Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface;
+
+/**
+ * Allows to compile and evaluate expressions written in your own DSL.
+ *
+ * @author Fabien Potencier
+ */
+class ExpressionLanguage
+{
+ private $cache;
+ private $lexer;
+ private $parser;
+ private $compiler;
+
+ protected $functions = [];
+
+ /**
+ * @param CacheItemPoolInterface $cache
+ * @param ExpressionFunctionProviderInterface[] $providers
+ */
+ public function __construct($cache = null, array $providers = [])
+ {
+ if (null !== $cache) {
+ if ($cache instanceof ParserCacheInterface) {
+ @trigger_error(sprintf('Passing an instance of %s as constructor argument for %s is deprecated as of 3.2 and will be removed in 4.0. Pass an instance of %s instead.', ParserCacheInterface::class, self::class, CacheItemPoolInterface::class), E_USER_DEPRECATED);
+ $cache = new ParserCacheAdapter($cache);
+ } elseif (!$cache instanceof CacheItemPoolInterface) {
+ throw new \InvalidArgumentException(sprintf('Cache argument has to implement %s.', CacheItemPoolInterface::class));
+ }
+ }
+
+ $this->cache = $cache ?: new ArrayAdapter();
+ $this->registerFunctions();
+ foreach ($providers as $provider) {
+ $this->registerProvider($provider);
+ }
+ }
+
+ /**
+ * Compiles an expression source code.
+ *
+ * @param Expression|string $expression The expression to compile
+ * @param array $names An array of valid names
+ *
+ * @return string The compiled PHP source code
+ */
+ public function compile($expression, $names = [])
+ {
+ return $this->getCompiler()->compile($this->parse($expression, $names)->getNodes())->getSource();
+ }
+
+ /**
+ * Evaluate an expression.
+ *
+ * @param Expression|string $expression The expression to compile
+ * @param array $values An array of values
+ *
+ * @return mixed The result of the evaluation of the expression
+ */
+ public function evaluate($expression, $values = [])
+ {
+ return $this->parse($expression, array_keys($values))->getNodes()->evaluate($this->functions, $values);
+ }
+
+ /**
+ * Parses an expression.
+ *
+ * @param Expression|string $expression The expression to parse
+ * @param array $names An array of valid names
+ *
+ * @return ParsedExpression A ParsedExpression instance
+ */
+ public function parse($expression, $names)
+ {
+ if ($expression instanceof ParsedExpression) {
+ return $expression;
+ }
+
+ asort($names);
+ $cacheKeyItems = [];
+
+ foreach ($names as $nameKey => $name) {
+ $cacheKeyItems[] = \is_int($nameKey) ? $name : $nameKey.':'.$name;
+ }
+
+ $cacheItem = $this->cache->getItem(rawurlencode($expression.'//'.implode('|', $cacheKeyItems)));
+
+ if (null === $parsedExpression = $cacheItem->get()) {
+ $nodes = $this->getParser()->parse($this->getLexer()->tokenize((string) $expression), $names);
+ $parsedExpression = new ParsedExpression((string) $expression, $nodes);
+
+ $cacheItem->set($parsedExpression);
+ $this->cache->save($cacheItem);
+ }
+
+ return $parsedExpression;
+ }
+
+ /**
+ * Registers a function.
+ *
+ * @param string $name The function name
+ * @param callable $compiler A callable able to compile the function
+ * @param callable $evaluator A callable able to evaluate the function
+ *
+ * @throws \LogicException when registering a function after calling evaluate(), compile() or parse()
+ *
+ * @see ExpressionFunction
+ */
+ public function register($name, callable $compiler, callable $evaluator)
+ {
+ if (null !== $this->parser) {
+ throw new \LogicException('Registering functions after calling evaluate(), compile() or parse() is not supported.');
+ }
+
+ $this->functions[$name] = ['compiler' => $compiler, 'evaluator' => $evaluator];
+ }
+
+ public function addFunction(ExpressionFunction $function)
+ {
+ $this->register($function->getName(), $function->getCompiler(), $function->getEvaluator());
+ }
+
+ public function registerProvider(ExpressionFunctionProviderInterface $provider)
+ {
+ foreach ($provider->getFunctions() as $function) {
+ $this->addFunction($function);
+ }
+ }
+
+ protected function registerFunctions()
+ {
+ $this->addFunction(ExpressionFunction::fromPhp('constant'));
+ }
+
+ private function getLexer()
+ {
+ if (null === $this->lexer) {
+ $this->lexer = new Lexer();
+ }
+
+ return $this->lexer;
+ }
+
+ private function getParser()
+ {
+ if (null === $this->parser) {
+ $this->parser = new Parser($this->functions);
+ }
+
+ return $this->parser;
+ }
+
+ private function getCompiler()
+ {
+ if (null === $this->compiler) {
+ $this->compiler = new Compiler($this->functions);
+ }
+
+ return $this->compiler->reset();
+ }
+}
diff --git a/console/skel/symfony/expression-language/LICENSE b/console/skel/symfony/expression-language/LICENSE
new file mode 100644
index 0000000..9e936ec
--- /dev/null
+++ b/console/skel/symfony/expression-language/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2020 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/console/skel/symfony/expression-language/Lexer.php b/console/skel/symfony/expression-language/Lexer.php
new file mode 100644
index 0000000..01ca293
--- /dev/null
+++ b/console/skel/symfony/expression-language/Lexer.php
@@ -0,0 +1,103 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage;
+
+/**
+ * Lexes an expression.
+ *
+ * @author Fabien Potencier
+ */
+class Lexer
+{
+ /**
+ * Tokenizes an expression.
+ *
+ * @param string $expression The expression to tokenize
+ *
+ * @return TokenStream A token stream instance
+ *
+ * @throws SyntaxError
+ */
+ public function tokenize($expression)
+ {
+ $expression = str_replace(["\r", "\n", "\t", "\v", "\f"], ' ', $expression);
+ $cursor = 0;
+ $tokens = [];
+ $brackets = [];
+ $end = \strlen($expression);
+
+ while ($cursor < $end) {
+ if (' ' == $expression[$cursor]) {
+ ++$cursor;
+
+ continue;
+ }
+
+ if (preg_match('/[0-9]+(?:\.[0-9]+)?/A', $expression, $match, 0, $cursor)) {
+ // numbers
+ $number = (float) $match[0]; // floats
+ if (preg_match('/^[0-9]+$/', $match[0]) && $number <= PHP_INT_MAX) {
+ $number = (int) $match[0]; // integers lower than the maximum
+ }
+ $tokens[] = new Token(Token::NUMBER_TYPE, $number, $cursor + 1);
+ $cursor += \strlen($match[0]);
+ } elseif (false !== strpos('([{', $expression[$cursor])) {
+ // opening bracket
+ $brackets[] = [$expression[$cursor], $cursor];
+
+ $tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1);
+ ++$cursor;
+ } elseif (false !== strpos(')]}', $expression[$cursor])) {
+ // closing bracket
+ if (empty($brackets)) {
+ throw new SyntaxError(sprintf('Unexpected "%s"', $expression[$cursor]), $cursor, $expression);
+ }
+
+ list($expect, $cur) = array_pop($brackets);
+ if ($expression[$cursor] != strtr($expect, '([{', ')]}')) {
+ throw new SyntaxError(sprintf('Unclosed "%s"', $expect), $cur, $expression);
+ }
+
+ $tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1);
+ ++$cursor;
+ } elseif (preg_match('/"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As', $expression, $match, 0, $cursor)) {
+ // strings
+ $tokens[] = new Token(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)), $cursor + 1);
+ $cursor += \strlen($match[0]);
+ } elseif (preg_match('/(?<=^|[\s(])not in(?=[\s(])|\!\=\=|(?<=^|[\s(])not(?=[\s(])|(?<=^|[\s(])and(?=[\s(])|\=\=\=|\>\=|(?<=^|[\s(])or(?=[\s(])|\<\=|\*\*|\.\.|(?<=^|[\s(])in(?=[\s(])|&&|\|\||(?<=^|[\s(])matches|\=\=|\!\=|\*|~|%|\/|\>|\||\!|\^|&|\+|\<|\-/A', $expression, $match, 0, $cursor)) {
+ // operators
+ $tokens[] = new Token(Token::OPERATOR_TYPE, $match[0], $cursor + 1);
+ $cursor += \strlen($match[0]);
+ } elseif (false !== strpos('.,?:', $expression[$cursor])) {
+ // punctuation
+ $tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1);
+ ++$cursor;
+ } elseif (preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $expression, $match, 0, $cursor)) {
+ // names
+ $tokens[] = new Token(Token::NAME_TYPE, $match[0], $cursor + 1);
+ $cursor += \strlen($match[0]);
+ } else {
+ // unlexable
+ throw new SyntaxError(sprintf('Unexpected character "%s"', $expression[$cursor]), $cursor, $expression);
+ }
+ }
+
+ $tokens[] = new Token(Token::EOF_TYPE, null, $cursor + 1);
+
+ if (!empty($brackets)) {
+ list($expect, $cur) = array_pop($brackets);
+ throw new SyntaxError(sprintf('Unclosed "%s"', $expect), $cur, $expression);
+ }
+
+ return new TokenStream($tokens, $expression);
+ }
+}
diff --git a/console/skel/symfony/expression-language/Node/ArgumentsNode.php b/console/skel/symfony/expression-language/Node/ArgumentsNode.php
new file mode 100644
index 0000000..e9849a4
--- /dev/null
+++ b/console/skel/symfony/expression-language/Node/ArgumentsNode.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Node;
+
+use Symfony\Component\ExpressionLanguage\Compiler;
+
+/**
+ * @author Fabien Potencier
+ *
+ * @internal
+ */
+class ArgumentsNode extends ArrayNode
+{
+ public function compile(Compiler $compiler)
+ {
+ $this->compileArguments($compiler, false);
+ }
+
+ public function toArray()
+ {
+ $array = [];
+
+ foreach ($this->getKeyValuePairs() as $pair) {
+ $array[] = $pair['value'];
+ $array[] = ', ';
+ }
+ array_pop($array);
+
+ return $array;
+ }
+}
diff --git a/console/skel/symfony/expression-language/Node/ArrayNode.php b/console/skel/symfony/expression-language/Node/ArrayNode.php
new file mode 100644
index 0000000..921319a
--- /dev/null
+++ b/console/skel/symfony/expression-language/Node/ArrayNode.php
@@ -0,0 +1,118 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Node;
+
+use Symfony\Component\ExpressionLanguage\Compiler;
+
+/**
+ * @author Fabien Potencier
+ *
+ * @internal
+ */
+class ArrayNode extends Node
+{
+ protected $index;
+
+ public function __construct()
+ {
+ $this->index = -1;
+ }
+
+ public function addElement(Node $value, Node $key = null)
+ {
+ if (null === $key) {
+ $key = new ConstantNode(++$this->index);
+ }
+
+ array_push($this->nodes, $key, $value);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ */
+ public function compile(Compiler $compiler)
+ {
+ $compiler->raw('[');
+ $this->compileArguments($compiler);
+ $compiler->raw(']');
+ }
+
+ public function evaluate($functions, $values)
+ {
+ $result = [];
+ foreach ($this->getKeyValuePairs() as $pair) {
+ $result[$pair['key']->evaluate($functions, $values)] = $pair['value']->evaluate($functions, $values);
+ }
+
+ return $result;
+ }
+
+ public function toArray()
+ {
+ $value = [];
+ foreach ($this->getKeyValuePairs() as $pair) {
+ $value[$pair['key']->attributes['value']] = $pair['value'];
+ }
+
+ $array = [];
+
+ if ($this->isHash($value)) {
+ foreach ($value as $k => $v) {
+ $array[] = ', ';
+ $array[] = new ConstantNode($k);
+ $array[] = ': ';
+ $array[] = $v;
+ }
+ $array[0] = '{';
+ $array[] = '}';
+ } else {
+ foreach ($value as $v) {
+ $array[] = ', ';
+ $array[] = $v;
+ }
+ $array[0] = '[';
+ $array[] = ']';
+ }
+
+ return $array;
+ }
+
+ protected function getKeyValuePairs()
+ {
+ $pairs = [];
+ foreach (array_chunk($this->nodes, 2) as $pair) {
+ $pairs[] = ['key' => $pair[0], 'value' => $pair[1]];
+ }
+
+ return $pairs;
+ }
+
+ protected function compileArguments(Compiler $compiler, $withKeys = true)
+ {
+ $first = true;
+ foreach ($this->getKeyValuePairs() as $pair) {
+ if (!$first) {
+ $compiler->raw(', ');
+ }
+ $first = false;
+
+ if ($withKeys) {
+ $compiler
+ ->compile($pair['key'])
+ ->raw(' => ')
+ ;
+ }
+
+ $compiler->compile($pair['value']);
+ }
+ }
+}
diff --git a/console/skel/symfony/expression-language/Node/BinaryNode.php b/console/skel/symfony/expression-language/Node/BinaryNode.php
new file mode 100644
index 0000000..bbfe393
--- /dev/null
+++ b/console/skel/symfony/expression-language/Node/BinaryNode.php
@@ -0,0 +1,170 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Node;
+
+use Symfony\Component\ExpressionLanguage\Compiler;
+
+/**
+ * @author Fabien Potencier
+ *
+ * @internal
+ */
+class BinaryNode extends Node
+{
+ private static $operators = [
+ '~' => '.',
+ 'and' => '&&',
+ 'or' => '||',
+ ];
+
+ private static $functions = [
+ '**' => 'pow',
+ '..' => 'range',
+ 'in' => 'in_array',
+ 'not in' => '!in_array',
+ ];
+
+ public function __construct($operator, Node $left, Node $right)
+ {
+ parent::__construct(
+ ['left' => $left, 'right' => $right],
+ ['operator' => $operator]
+ );
+ }
+
+ public function compile(Compiler $compiler)
+ {
+ $operator = $this->attributes['operator'];
+
+ if ('matches' == $operator) {
+ $compiler
+ ->raw('preg_match(')
+ ->compile($this->nodes['right'])
+ ->raw(', ')
+ ->compile($this->nodes['left'])
+ ->raw(')')
+ ;
+
+ return;
+ }
+
+ if (isset(self::$functions[$operator])) {
+ $compiler
+ ->raw(sprintf('%s(', self::$functions[$operator]))
+ ->compile($this->nodes['left'])
+ ->raw(', ')
+ ->compile($this->nodes['right'])
+ ->raw(')')
+ ;
+
+ return;
+ }
+
+ if (isset(self::$operators[$operator])) {
+ $operator = self::$operators[$operator];
+ }
+
+ $compiler
+ ->raw('(')
+ ->compile($this->nodes['left'])
+ ->raw(' ')
+ ->raw($operator)
+ ->raw(' ')
+ ->compile($this->nodes['right'])
+ ->raw(')')
+ ;
+ }
+
+ public function evaluate($functions, $values)
+ {
+ $operator = $this->attributes['operator'];
+ $left = $this->nodes['left']->evaluate($functions, $values);
+
+ if (isset(self::$functions[$operator])) {
+ $right = $this->nodes['right']->evaluate($functions, $values);
+
+ if ('not in' === $operator) {
+ return !\in_array($left, $right);
+ }
+ $f = self::$functions[$operator];
+
+ return $f($left, $right);
+ }
+
+ switch ($operator) {
+ case 'or':
+ case '||':
+ return $left || $this->nodes['right']->evaluate($functions, $values);
+ case 'and':
+ case '&&':
+ return $left && $this->nodes['right']->evaluate($functions, $values);
+ }
+
+ $right = $this->nodes['right']->evaluate($functions, $values);
+
+ switch ($operator) {
+ case '|':
+ return $left | $right;
+ case '^':
+ return $left ^ $right;
+ case '&':
+ return $left & $right;
+ case '==':
+ return $left == $right;
+ case '===':
+ return $left === $right;
+ case '!=':
+ return $left != $right;
+ case '!==':
+ return $left !== $right;
+ case '<':
+ return $left < $right;
+ case '>':
+ return $left > $right;
+ case '>=':
+ return $left >= $right;
+ case '<=':
+ return $left <= $right;
+ case 'not in':
+ return !\in_array($left, $right);
+ case 'in':
+ return \in_array($left, $right);
+ case '+':
+ return $left + $right;
+ case '-':
+ return $left - $right;
+ case '~':
+ return $left.$right;
+ case '*':
+ return $left * $right;
+ case '/':
+ if (0 == $right) {
+ throw new \DivisionByZeroError('Division by zero');
+ }
+
+ return $left / $right;
+ case '%':
+ if (0 == $right) {
+ throw new \DivisionByZeroError('Modulo by zero');
+ }
+
+ return $left % $right;
+ case 'matches':
+ return preg_match($right, $left);
+ }
+ }
+
+ public function toArray()
+ {
+ return ['(', $this->nodes['left'], ' '.$this->attributes['operator'].' ', $this->nodes['right'], ')'];
+ }
+}
diff --git a/console/skel/symfony/expression-language/Node/ConditionalNode.php b/console/skel/symfony/expression-language/Node/ConditionalNode.php
new file mode 100644
index 0000000..ca1b484
--- /dev/null
+++ b/console/skel/symfony/expression-language/Node/ConditionalNode.php
@@ -0,0 +1,56 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Node;
+
+use Symfony\Component\ExpressionLanguage\Compiler;
+
+/**
+ * @author Fabien Potencier
+ *
+ * @internal
+ */
+class ConditionalNode extends Node
+{
+ public function __construct(Node $expr1, Node $expr2, Node $expr3)
+ {
+ parent::__construct(
+ ['expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3]
+ );
+ }
+
+ public function compile(Compiler $compiler)
+ {
+ $compiler
+ ->raw('((')
+ ->compile($this->nodes['expr1'])
+ ->raw(') ? (')
+ ->compile($this->nodes['expr2'])
+ ->raw(') : (')
+ ->compile($this->nodes['expr3'])
+ ->raw('))')
+ ;
+ }
+
+ public function evaluate($functions, $values)
+ {
+ if ($this->nodes['expr1']->evaluate($functions, $values)) {
+ return $this->nodes['expr2']->evaluate($functions, $values);
+ }
+
+ return $this->nodes['expr3']->evaluate($functions, $values);
+ }
+
+ public function toArray()
+ {
+ return ['(', $this->nodes['expr1'], ' ? ', $this->nodes['expr2'], ' : ', $this->nodes['expr3'], ')'];
+ }
+}
diff --git a/console/skel/symfony/expression-language/Node/ConstantNode.php b/console/skel/symfony/expression-language/Node/ConstantNode.php
new file mode 100644
index 0000000..670e52e
--- /dev/null
+++ b/console/skel/symfony/expression-language/Node/ConstantNode.php
@@ -0,0 +1,81 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Node;
+
+use Symfony\Component\ExpressionLanguage\Compiler;
+
+/**
+ * @author Fabien Potencier
+ *
+ * @internal
+ */
+class ConstantNode extends Node
+{
+ private $isIdentifier;
+
+ public function __construct($value, $isIdentifier = false)
+ {
+ $this->isIdentifier = $isIdentifier;
+ parent::__construct(
+ [],
+ ['value' => $value]
+ );
+ }
+
+ public function compile(Compiler $compiler)
+ {
+ $compiler->repr($this->attributes['value']);
+ }
+
+ public function evaluate($functions, $values)
+ {
+ return $this->attributes['value'];
+ }
+
+ public function toArray()
+ {
+ $array = [];
+ $value = $this->attributes['value'];
+
+ if ($this->isIdentifier) {
+ $array[] = $value;
+ } elseif (true === $value) {
+ $array[] = 'true';
+ } elseif (false === $value) {
+ $array[] = 'false';
+ } elseif (null === $value) {
+ $array[] = 'null';
+ } elseif (is_numeric($value)) {
+ $array[] = $value;
+ } elseif (!\is_array($value)) {
+ $array[] = $this->dumpString($value);
+ } elseif ($this->isHash($value)) {
+ foreach ($value as $k => $v) {
+ $array[] = ', ';
+ $array[] = new self($k);
+ $array[] = ': ';
+ $array[] = new self($v);
+ }
+ $array[0] = '{';
+ $array[] = '}';
+ } else {
+ foreach ($value as $v) {
+ $array[] = ', ';
+ $array[] = new self($v);
+ }
+ $array[0] = '[';
+ $array[] = ']';
+ }
+
+ return $array;
+ }
+}
diff --git a/console/skel/symfony/expression-language/Node/FunctionNode.php b/console/skel/symfony/expression-language/Node/FunctionNode.php
new file mode 100644
index 0000000..90008d5
--- /dev/null
+++ b/console/skel/symfony/expression-language/Node/FunctionNode.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Node;
+
+use Symfony\Component\ExpressionLanguage\Compiler;
+
+/**
+ * @author Fabien Potencier
+ *
+ * @internal
+ */
+class FunctionNode extends Node
+{
+ public function __construct($name, Node $arguments)
+ {
+ parent::__construct(
+ ['arguments' => $arguments],
+ ['name' => $name]
+ );
+ }
+
+ public function compile(Compiler $compiler)
+ {
+ $arguments = [];
+ foreach ($this->nodes['arguments']->nodes as $node) {
+ $arguments[] = $compiler->subcompile($node);
+ }
+
+ $function = $compiler->getFunction($this->attributes['name']);
+
+ $compiler->raw(\call_user_func_array($function['compiler'], $arguments));
+ }
+
+ public function evaluate($functions, $values)
+ {
+ $arguments = [$values];
+ foreach ($this->nodes['arguments']->nodes as $node) {
+ $arguments[] = $node->evaluate($functions, $values);
+ }
+
+ return \call_user_func_array($functions[$this->attributes['name']]['evaluator'], $arguments);
+ }
+
+ public function toArray()
+ {
+ $array = [];
+ $array[] = $this->attributes['name'];
+
+ foreach ($this->nodes['arguments']->nodes as $node) {
+ $array[] = ', ';
+ $array[] = $node;
+ }
+ $array[1] = '(';
+ $array[] = ')';
+
+ return $array;
+ }
+}
diff --git a/console/skel/symfony/expression-language/Node/GetAttrNode.php b/console/skel/symfony/expression-language/Node/GetAttrNode.php
new file mode 100644
index 0000000..1cf414a
--- /dev/null
+++ b/console/skel/symfony/expression-language/Node/GetAttrNode.php
@@ -0,0 +1,114 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Node;
+
+use Symfony\Component\ExpressionLanguage\Compiler;
+
+/**
+ * @author Fabien Potencier
+ *
+ * @internal
+ */
+class GetAttrNode extends Node
+{
+ const PROPERTY_CALL = 1;
+ const METHOD_CALL = 2;
+ const ARRAY_CALL = 3;
+
+ public function __construct(Node $node, Node $attribute, ArrayNode $arguments, $type)
+ {
+ parent::__construct(
+ ['node' => $node, 'attribute' => $attribute, 'arguments' => $arguments],
+ ['type' => $type]
+ );
+ }
+
+ public function compile(Compiler $compiler)
+ {
+ switch ($this->attributes['type']) {
+ case self::PROPERTY_CALL:
+ $compiler
+ ->compile($this->nodes['node'])
+ ->raw('->')
+ ->raw($this->nodes['attribute']->attributes['value'])
+ ;
+ break;
+
+ case self::METHOD_CALL:
+ $compiler
+ ->compile($this->nodes['node'])
+ ->raw('->')
+ ->raw($this->nodes['attribute']->attributes['value'])
+ ->raw('(')
+ ->compile($this->nodes['arguments'])
+ ->raw(')')
+ ;
+ break;
+
+ case self::ARRAY_CALL:
+ $compiler
+ ->compile($this->nodes['node'])
+ ->raw('[')
+ ->compile($this->nodes['attribute'])->raw(']')
+ ;
+ break;
+ }
+ }
+
+ public function evaluate($functions, $values)
+ {
+ switch ($this->attributes['type']) {
+ case self::PROPERTY_CALL:
+ $obj = $this->nodes['node']->evaluate($functions, $values);
+ if (!\is_object($obj)) {
+ throw new \RuntimeException('Unable to get a property on a non-object.');
+ }
+
+ $property = $this->nodes['attribute']->attributes['value'];
+
+ return $obj->$property;
+
+ case self::METHOD_CALL:
+ $obj = $this->nodes['node']->evaluate($functions, $values);
+ if (!\is_object($obj)) {
+ throw new \RuntimeException('Unable to get a property on a non-object.');
+ }
+ if (!\is_callable($toCall = [$obj, $this->nodes['attribute']->attributes['value']])) {
+ throw new \RuntimeException(sprintf('Unable to call method "%s" of object "%s".', $this->nodes['attribute']->attributes['value'], \get_class($obj)));
+ }
+
+ return \call_user_func_array($toCall, $this->nodes['arguments']->evaluate($functions, $values));
+
+ case self::ARRAY_CALL:
+ $array = $this->nodes['node']->evaluate($functions, $values);
+ if (!\is_array($array) && !$array instanceof \ArrayAccess) {
+ throw new \RuntimeException('Unable to get an item on a non-array.');
+ }
+
+ return $array[$this->nodes['attribute']->evaluate($functions, $values)];
+ }
+ }
+
+ public function toArray()
+ {
+ switch ($this->attributes['type']) {
+ case self::PROPERTY_CALL:
+ return [$this->nodes['node'], '.', $this->nodes['attribute']];
+
+ case self::METHOD_CALL:
+ return [$this->nodes['node'], '.', $this->nodes['attribute'], '(', $this->nodes['arguments'], ')'];
+
+ case self::ARRAY_CALL:
+ return [$this->nodes['node'], '[', $this->nodes['attribute'], ']'];
+ }
+ }
+}
diff --git a/console/skel/symfony/expression-language/Node/NameNode.php b/console/skel/symfony/expression-language/Node/NameNode.php
new file mode 100644
index 0000000..1c9c277
--- /dev/null
+++ b/console/skel/symfony/expression-language/Node/NameNode.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Node;
+
+use Symfony\Component\ExpressionLanguage\Compiler;
+
+/**
+ * @author Fabien Potencier
+ *
+ * @internal
+ */
+class NameNode extends Node
+{
+ public function __construct($name)
+ {
+ parent::__construct(
+ [],
+ ['name' => $name]
+ );
+ }
+
+ public function compile(Compiler $compiler)
+ {
+ $compiler->raw('$'.$this->attributes['name']);
+ }
+
+ public function evaluate($functions, $values)
+ {
+ return $values[$this->attributes['name']];
+ }
+
+ public function toArray()
+ {
+ return [$this->attributes['name']];
+ }
+}
diff --git a/console/skel/symfony/expression-language/Node/Node.php b/console/skel/symfony/expression-language/Node/Node.php
new file mode 100644
index 0000000..9504590
--- /dev/null
+++ b/console/skel/symfony/expression-language/Node/Node.php
@@ -0,0 +1,110 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Node;
+
+use Symfony\Component\ExpressionLanguage\Compiler;
+
+/**
+ * Represents a node in the AST.
+ *
+ * @author Fabien Potencier
+ */
+class Node
+{
+ public $nodes = [];
+ public $attributes = [];
+
+ /**
+ * @param array $nodes An array of nodes
+ * @param array $attributes An array of attributes
+ */
+ public function __construct(array $nodes = [], array $attributes = [])
+ {
+ $this->nodes = $nodes;
+ $this->attributes = $attributes;
+ }
+
+ public function __toString()
+ {
+ $attributes = [];
+ foreach ($this->attributes as $name => $value) {
+ $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true)));
+ }
+
+ $repr = [str_replace('Symfony\Component\ExpressionLanguage\Node\\', '', static::class).'('.implode(', ', $attributes)];
+
+ if (\count($this->nodes)) {
+ foreach ($this->nodes as $node) {
+ foreach (explode("\n", (string) $node) as $line) {
+ $repr[] = ' '.$line;
+ }
+ }
+
+ $repr[] = ')';
+ } else {
+ $repr[0] .= ')';
+ }
+
+ return implode("\n", $repr);
+ }
+
+ public function compile(Compiler $compiler)
+ {
+ foreach ($this->nodes as $node) {
+ $node->compile($compiler);
+ }
+ }
+
+ public function evaluate($functions, $values)
+ {
+ $results = [];
+ foreach ($this->nodes as $node) {
+ $results[] = $node->evaluate($functions, $values);
+ }
+
+ return $results;
+ }
+
+ public function toArray()
+ {
+ throw new \BadMethodCallException(sprintf('Dumping a "%s" instance is not supported yet.', static::class));
+ }
+
+ public function dump()
+ {
+ $dump = '';
+
+ foreach ($this->toArray() as $v) {
+ $dump .= is_scalar($v) ? $v : $v->dump();
+ }
+
+ return $dump;
+ }
+
+ protected function dumpString($value)
+ {
+ return sprintf('"%s"', addcslashes($value, "\0\t\"\\"));
+ }
+
+ protected function isHash(array $value)
+ {
+ $expectedKey = 0;
+
+ foreach ($value as $key => $val) {
+ if ($key !== $expectedKey++) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/console/skel/symfony/expression-language/Node/UnaryNode.php b/console/skel/symfony/expression-language/Node/UnaryNode.php
new file mode 100644
index 0000000..497b7fe
--- /dev/null
+++ b/console/skel/symfony/expression-language/Node/UnaryNode.php
@@ -0,0 +1,66 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Node;
+
+use Symfony\Component\ExpressionLanguage\Compiler;
+
+/**
+ * @author Fabien Potencier
+ *
+ * @internal
+ */
+class UnaryNode extends Node
+{
+ private static $operators = [
+ '!' => '!',
+ 'not' => '!',
+ '+' => '+',
+ '-' => '-',
+ ];
+
+ public function __construct($operator, Node $node)
+ {
+ parent::__construct(
+ ['node' => $node],
+ ['operator' => $operator]
+ );
+ }
+
+ public function compile(Compiler $compiler)
+ {
+ $compiler
+ ->raw('(')
+ ->raw(self::$operators[$this->attributes['operator']])
+ ->compile($this->nodes['node'])
+ ->raw(')')
+ ;
+ }
+
+ public function evaluate($functions, $values)
+ {
+ $value = $this->nodes['node']->evaluate($functions, $values);
+ switch ($this->attributes['operator']) {
+ case 'not':
+ case '!':
+ return !$value;
+ case '-':
+ return -$value;
+ }
+
+ return $value;
+ }
+
+ public function toArray()
+ {
+ return ['(', $this->attributes['operator'].' ', $this->nodes['node'], ')'];
+ }
+}
diff --git a/console/skel/symfony/expression-language/ParsedExpression.php b/console/skel/symfony/expression-language/ParsedExpression.php
new file mode 100644
index 0000000..a5603fc
--- /dev/null
+++ b/console/skel/symfony/expression-language/ParsedExpression.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage;
+
+use Symfony\Component\ExpressionLanguage\Node\Node;
+
+/**
+ * Represents an already parsed expression.
+ *
+ * @author Fabien Potencier
+ */
+class ParsedExpression extends Expression
+{
+ private $nodes;
+
+ /**
+ * @param string $expression An expression
+ * @param Node $nodes A Node representing the expression
+ */
+ public function __construct($expression, Node $nodes)
+ {
+ parent::__construct($expression);
+
+ $this->nodes = $nodes;
+ }
+
+ public function getNodes()
+ {
+ return $this->nodes;
+ }
+}
diff --git a/console/skel/symfony/expression-language/Parser.php b/console/skel/symfony/expression-language/Parser.php
new file mode 100644
index 0000000..0930bac
--- /dev/null
+++ b/console/skel/symfony/expression-language/Parser.php
@@ -0,0 +1,380 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage;
+
+/**
+ * Parsers a token stream.
+ *
+ * This parser implements a "Precedence climbing" algorithm.
+ *
+ * @see http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm
+ * @see http://en.wikipedia.org/wiki/Operator-precedence_parser
+ *
+ * @author Fabien Potencier
+ */
+class Parser
+{
+ const OPERATOR_LEFT = 1;
+ const OPERATOR_RIGHT = 2;
+
+ private $stream;
+ private $unaryOperators;
+ private $binaryOperators;
+ private $functions;
+ private $names;
+
+ public function __construct(array $functions)
+ {
+ $this->functions = $functions;
+
+ $this->unaryOperators = [
+ 'not' => ['precedence' => 50],
+ '!' => ['precedence' => 50],
+ '-' => ['precedence' => 500],
+ '+' => ['precedence' => 500],
+ ];
+ $this->binaryOperators = [
+ 'or' => ['precedence' => 10, 'associativity' => self::OPERATOR_LEFT],
+ '||' => ['precedence' => 10, 'associativity' => self::OPERATOR_LEFT],
+ 'and' => ['precedence' => 15, 'associativity' => self::OPERATOR_LEFT],
+ '&&' => ['precedence' => 15, 'associativity' => self::OPERATOR_LEFT],
+ '|' => ['precedence' => 16, 'associativity' => self::OPERATOR_LEFT],
+ '^' => ['precedence' => 17, 'associativity' => self::OPERATOR_LEFT],
+ '&' => ['precedence' => 18, 'associativity' => self::OPERATOR_LEFT],
+ '==' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT],
+ '===' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT],
+ '!=' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT],
+ '!==' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT],
+ '<' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT],
+ '>' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT],
+ '>=' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT],
+ '<=' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT],
+ 'not in' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT],
+ 'in' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT],
+ 'matches' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT],
+ '..' => ['precedence' => 25, 'associativity' => self::OPERATOR_LEFT],
+ '+' => ['precedence' => 30, 'associativity' => self::OPERATOR_LEFT],
+ '-' => ['precedence' => 30, 'associativity' => self::OPERATOR_LEFT],
+ '~' => ['precedence' => 40, 'associativity' => self::OPERATOR_LEFT],
+ '*' => ['precedence' => 60, 'associativity' => self::OPERATOR_LEFT],
+ '/' => ['precedence' => 60, 'associativity' => self::OPERATOR_LEFT],
+ '%' => ['precedence' => 60, 'associativity' => self::OPERATOR_LEFT],
+ '**' => ['precedence' => 200, 'associativity' => self::OPERATOR_RIGHT],
+ ];
+ }
+
+ /**
+ * Converts a token stream to a node tree.
+ *
+ * The valid names is an array where the values
+ * are the names that the user can use in an expression.
+ *
+ * If the variable name in the compiled PHP code must be
+ * different, define it as the key.
+ *
+ * For instance, ['this' => 'container'] means that the
+ * variable 'container' can be used in the expression
+ * but the compiled code will use 'this'.
+ *
+ * @param TokenStream $stream A token stream instance
+ * @param array $names An array of valid names
+ *
+ * @return Node\Node A node tree
+ *
+ * @throws SyntaxError
+ */
+ public function parse(TokenStream $stream, $names = [])
+ {
+ $this->stream = $stream;
+ $this->names = $names;
+
+ $node = $this->parseExpression();
+ if (!$stream->isEOF()) {
+ throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s"', $stream->current->type, $stream->current->value), $stream->current->cursor, $stream->getExpression());
+ }
+
+ return $node;
+ }
+
+ public function parseExpression($precedence = 0)
+ {
+ $expr = $this->getPrimary();
+ $token = $this->stream->current;
+ while ($token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->value]) && $this->binaryOperators[$token->value]['precedence'] >= $precedence) {
+ $op = $this->binaryOperators[$token->value];
+ $this->stream->next();
+
+ $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
+ $expr = new Node\BinaryNode($token->value, $expr, $expr1);
+
+ $token = $this->stream->current;
+ }
+
+ if (0 === $precedence) {
+ return $this->parseConditionalExpression($expr);
+ }
+
+ return $expr;
+ }
+
+ protected function getPrimary()
+ {
+ $token = $this->stream->current;
+
+ if ($token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->value])) {
+ $operator = $this->unaryOperators[$token->value];
+ $this->stream->next();
+ $expr = $this->parseExpression($operator['precedence']);
+
+ return $this->parsePostfixExpression(new Node\UnaryNode($token->value, $expr));
+ }
+
+ if ($token->test(Token::PUNCTUATION_TYPE, '(')) {
+ $this->stream->next();
+ $expr = $this->parseExpression();
+ $this->stream->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed');
+
+ return $this->parsePostfixExpression($expr);
+ }
+
+ return $this->parsePrimaryExpression();
+ }
+
+ protected function parseConditionalExpression($expr)
+ {
+ while ($this->stream->current->test(Token::PUNCTUATION_TYPE, '?')) {
+ $this->stream->next();
+ if (!$this->stream->current->test(Token::PUNCTUATION_TYPE, ':')) {
+ $expr2 = $this->parseExpression();
+ if ($this->stream->current->test(Token::PUNCTUATION_TYPE, ':')) {
+ $this->stream->next();
+ $expr3 = $this->parseExpression();
+ } else {
+ $expr3 = new Node\ConstantNode(null);
+ }
+ } else {
+ $this->stream->next();
+ $expr2 = $expr;
+ $expr3 = $this->parseExpression();
+ }
+
+ $expr = new Node\ConditionalNode($expr, $expr2, $expr3);
+ }
+
+ return $expr;
+ }
+
+ public function parsePrimaryExpression()
+ {
+ $token = $this->stream->current;
+ switch ($token->type) {
+ case Token::NAME_TYPE:
+ $this->stream->next();
+ switch ($token->value) {
+ case 'true':
+ case 'TRUE':
+ return new Node\ConstantNode(true);
+
+ case 'false':
+ case 'FALSE':
+ return new Node\ConstantNode(false);
+
+ case 'null':
+ case 'NULL':
+ return new Node\ConstantNode(null);
+
+ default:
+ if ('(' === $this->stream->current->value) {
+ if (false === isset($this->functions[$token->value])) {
+ throw new SyntaxError(sprintf('The function "%s" does not exist', $token->value), $token->cursor, $this->stream->getExpression(), $token->value, array_keys($this->functions));
+ }
+
+ $node = new Node\FunctionNode($token->value, $this->parseArguments());
+ } else {
+ if (!\in_array($token->value, $this->names, true)) {
+ throw new SyntaxError(sprintf('Variable "%s" is not valid', $token->value), $token->cursor, $this->stream->getExpression(), $token->value, $this->names);
+ }
+
+ // is the name used in the compiled code different
+ // from the name used in the expression?
+ if (\is_int($name = array_search($token->value, $this->names))) {
+ $name = $token->value;
+ }
+
+ $node = new Node\NameNode($name);
+ }
+ }
+ break;
+
+ case Token::NUMBER_TYPE:
+ case Token::STRING_TYPE:
+ $this->stream->next();
+
+ return new Node\ConstantNode($token->value);
+
+ default:
+ if ($token->test(Token::PUNCTUATION_TYPE, '[')) {
+ $node = $this->parseArrayExpression();
+ } elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) {
+ $node = $this->parseHashExpression();
+ } else {
+ throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s"', $token->type, $token->value), $token->cursor, $this->stream->getExpression());
+ }
+ }
+
+ return $this->parsePostfixExpression($node);
+ }
+
+ public function parseArrayExpression()
+ {
+ $this->stream->expect(Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
+
+ $node = new Node\ArrayNode();
+ $first = true;
+ while (!$this->stream->current->test(Token::PUNCTUATION_TYPE, ']')) {
+ if (!$first) {
+ $this->stream->expect(Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
+
+ // trailing ,?
+ if ($this->stream->current->test(Token::PUNCTUATION_TYPE, ']')) {
+ break;
+ }
+ }
+ $first = false;
+
+ $node->addElement($this->parseExpression());
+ }
+ $this->stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
+
+ return $node;
+ }
+
+ public function parseHashExpression()
+ {
+ $this->stream->expect(Token::PUNCTUATION_TYPE, '{', 'A hash element was expected');
+
+ $node = new Node\ArrayNode();
+ $first = true;
+ while (!$this->stream->current->test(Token::PUNCTUATION_TYPE, '}')) {
+ if (!$first) {
+ $this->stream->expect(Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma');
+
+ // trailing ,?
+ if ($this->stream->current->test(Token::PUNCTUATION_TYPE, '}')) {
+ break;
+ }
+ }
+ $first = false;
+
+ // a hash key can be:
+ //
+ // * a number -- 12
+ // * a string -- 'a'
+ // * a name, which is equivalent to a string -- a
+ // * an expression, which must be enclosed in parentheses -- (1 + 2)
+ if ($this->stream->current->test(Token::STRING_TYPE) || $this->stream->current->test(Token::NAME_TYPE) || $this->stream->current->test(Token::NUMBER_TYPE)) {
+ $key = new Node\ConstantNode($this->stream->current->value);
+ $this->stream->next();
+ } elseif ($this->stream->current->test(Token::PUNCTUATION_TYPE, '(')) {
+ $key = $this->parseExpression();
+ } else {
+ $current = $this->stream->current;
+
+ throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', $current->type, $current->value), $current->cursor, $this->stream->getExpression());
+ }
+
+ $this->stream->expect(Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
+ $value = $this->parseExpression();
+
+ $node->addElement($value, $key);
+ }
+ $this->stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed');
+
+ return $node;
+ }
+
+ public function parsePostfixExpression($node)
+ {
+ $token = $this->stream->current;
+ while (Token::PUNCTUATION_TYPE == $token->type) {
+ if ('.' === $token->value) {
+ $this->stream->next();
+ $token = $this->stream->current;
+ $this->stream->next();
+
+ if (
+ Token::NAME_TYPE !== $token->type
+ &&
+ // Operators like "not" and "matches" are valid method or property names,
+ //
+ // In other words, besides NAME_TYPE, OPERATOR_TYPE could also be parsed as a property or method.
+ // This is because operators are processed by the lexer prior to names. So "not" in "foo.not()" or "matches" in "foo.matches" will be recognized as an operator first.
+ // But in fact, "not" and "matches" in such expressions shall be parsed as method or property names.
+ //
+ // And this ONLY works if the operator consists of valid characters for a property or method name.
+ //
+ // Other types, such as STRING_TYPE and NUMBER_TYPE, can't be parsed as property nor method names.
+ //
+ // As a result, if $token is NOT an operator OR $token->value is NOT a valid property or method name, an exception shall be thrown.
+ (Token::OPERATOR_TYPE !== $token->type || !preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $token->value))
+ ) {
+ throw new SyntaxError('Expected name', $token->cursor, $this->stream->getExpression());
+ }
+
+ $arg = new Node\ConstantNode($token->value, true);
+
+ $arguments = new Node\ArgumentsNode();
+ if ($this->stream->current->test(Token::PUNCTUATION_TYPE, '(')) {
+ $type = Node\GetAttrNode::METHOD_CALL;
+ foreach ($this->parseArguments()->nodes as $n) {
+ $arguments->addElement($n);
+ }
+ } else {
+ $type = Node\GetAttrNode::PROPERTY_CALL;
+ }
+
+ $node = new Node\GetAttrNode($node, $arg, $arguments, $type);
+ } elseif ('[' === $token->value) {
+ $this->stream->next();
+ $arg = $this->parseExpression();
+ $this->stream->expect(Token::PUNCTUATION_TYPE, ']');
+
+ $node = new Node\GetAttrNode($node, $arg, new Node\ArgumentsNode(), Node\GetAttrNode::ARRAY_CALL);
+ } else {
+ break;
+ }
+
+ $token = $this->stream->current;
+ }
+
+ return $node;
+ }
+
+ /**
+ * Parses arguments.
+ */
+ public function parseArguments()
+ {
+ $args = [];
+ $this->stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
+ while (!$this->stream->current->test(Token::PUNCTUATION_TYPE, ')')) {
+ if (!empty($args)) {
+ $this->stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
+ }
+
+ $args[] = $this->parseExpression();
+ }
+ $this->stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
+
+ return new Node\Node($args);
+ }
+}
diff --git a/console/skel/symfony/expression-language/ParserCache/ArrayParserCache.php b/console/skel/symfony/expression-language/ParserCache/ArrayParserCache.php
new file mode 100644
index 0000000..8f9d9f4
--- /dev/null
+++ b/console/skel/symfony/expression-language/ParserCache/ArrayParserCache.php
@@ -0,0 +1,42 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\ParserCache;
+
+@trigger_error('The '.__NAMESPACE__.'\ArrayParserCache class is deprecated since Symfony 3.2 and will be removed in 4.0. Use the Symfony\Component\Cache\Adapter\ArrayAdapter class instead.', E_USER_DEPRECATED);
+
+use Symfony\Component\ExpressionLanguage\ParsedExpression;
+
+/**
+ * @author Adrien Brault
+ *
+ * @deprecated ArrayParserCache class is deprecated since version 3.2 and will be removed in 4.0. Use the Symfony\Component\Cache\Adapter\ArrayAdapter class instead.
+ */
+class ArrayParserCache implements ParserCacheInterface
+{
+ private $cache = [];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetch($key)
+ {
+ return isset($this->cache[$key]) ? $this->cache[$key] : null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save($key, ParsedExpression $expression)
+ {
+ $this->cache[$key] = $expression;
+ }
+}
diff --git a/console/skel/symfony/expression-language/ParserCache/ParserCacheAdapter.php b/console/skel/symfony/expression-language/ParserCache/ParserCacheAdapter.php
new file mode 100644
index 0000000..38ce659
--- /dev/null
+++ b/console/skel/symfony/expression-language/ParserCache/ParserCacheAdapter.php
@@ -0,0 +1,120 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\ParserCache;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\CacheItem;
+
+/**
+ * @author Alexandre GESLIN
+ *
+ * @internal and will be removed in Symfony 4.0.
+ */
+class ParserCacheAdapter implements CacheItemPoolInterface
+{
+ private $pool;
+ private $createCacheItem;
+
+ public function __construct(ParserCacheInterface $pool)
+ {
+ $this->pool = $pool;
+
+ $this->createCacheItem = \Closure::bind(
+ static function ($key, $value, $isHit) {
+ $item = new CacheItem();
+ $item->key = $key;
+ $item->value = $value;
+ $item->isHit = $isHit;
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ $value = $this->pool->fetch($key);
+ $f = $this->createCacheItem;
+
+ return $f($key, $value, null !== $value);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save(CacheItemInterface $item)
+ {
+ $this->pool->save($item->getKey(), $item->get());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ throw new \BadMethodCallException('Not implemented');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasItem($key)
+ {
+ throw new \BadMethodCallException('Not implemented');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ throw new \BadMethodCallException('Not implemented');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteItem($key)
+ {
+ throw new \BadMethodCallException('Not implemented');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteItems(array $keys)
+ {
+ throw new \BadMethodCallException('Not implemented');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ throw new \BadMethodCallException('Not implemented');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function commit()
+ {
+ throw new \BadMethodCallException('Not implemented');
+ }
+}
diff --git a/console/skel/symfony/expression-language/ParserCache/ParserCacheInterface.php b/console/skel/symfony/expression-language/ParserCache/ParserCacheInterface.php
new file mode 100644
index 0000000..ed66b21
--- /dev/null
+++ b/console/skel/symfony/expression-language/ParserCache/ParserCacheInterface.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\ParserCache;
+
+@trigger_error('The '.__NAMESPACE__.'\ParserCacheInterface interface is deprecated since Symfony 3.2 and will be removed in 4.0. Use Psr\Cache\CacheItemPoolInterface instead.', E_USER_DEPRECATED);
+
+use Symfony\Component\ExpressionLanguage\ParsedExpression;
+
+/**
+ * @author Adrien Brault
+ *
+ * @deprecated since version 3.2, to be removed in 4.0. Use Psr\Cache\CacheItemPoolInterface instead.
+ */
+interface ParserCacheInterface
+{
+ /**
+ * Saves an expression in the cache.
+ *
+ * @param string $key The cache key
+ * @param ParsedExpression $expression A ParsedExpression instance to store in the cache
+ */
+ public function save($key, ParsedExpression $expression);
+
+ /**
+ * Fetches an expression from the cache.
+ *
+ * @param string $key The cache key
+ *
+ * @return ParsedExpression|null
+ */
+ public function fetch($key);
+}
diff --git a/console/skel/symfony/expression-language/README.md b/console/skel/symfony/expression-language/README.md
new file mode 100644
index 0000000..08b310d
--- /dev/null
+++ b/console/skel/symfony/expression-language/README.md
@@ -0,0 +1,15 @@
+ExpressionLanguage Component
+============================
+
+The ExpressionLanguage component provides an engine that can compile and
+evaluate expressions. An expression is a one-liner that returns a value
+(mostly, but not limited to, Booleans).
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/expression_language/introduction.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/console/skel/symfony/expression-language/Resources/bin/generate_operator_regex.php b/console/skel/symfony/expression-language/Resources/bin/generate_operator_regex.php
new file mode 100644
index 0000000..c86e962
--- /dev/null
+++ b/console/skel/symfony/expression-language/Resources/bin/generate_operator_regex.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+$operators = ['not', '!', 'or', '||', '&&', 'and', '|', '^', '&', '==', '===', '!=', '!==', '<', '>', '>=', '<=', 'not in', 'in', '..', '+', '-', '~', '*', '/', '%', 'matches', '**'];
+$operators = array_combine($operators, array_map('strlen', $operators));
+arsort($operators);
+
+$regex = [];
+foreach ($operators as $operator => $length) {
+ // Collisions of character operators:
+ // - an operator that begins with a character must have a space or a parenthesis before or starting at the beginning of a string
+ // - an operator that ends with a character must be followed by a whitespace or a parenthesis
+ $regex[] =
+ (ctype_alpha($operator[0]) ? '(?<=^|[\s(])' : '')
+ .preg_quote($operator, '/')
+ .(ctype_alpha($operator[$length - 1]) ? '(?=[\s(])' : '');
+}
+
+echo '/'.implode('|', $regex).'/A';
diff --git a/console/skel/symfony/expression-language/SerializedParsedExpression.php b/console/skel/symfony/expression-language/SerializedParsedExpression.php
new file mode 100644
index 0000000..dd763f7
--- /dev/null
+++ b/console/skel/symfony/expression-language/SerializedParsedExpression.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage;
+
+/**
+ * Represents an already parsed expression.
+ *
+ * @author Fabien Potencier
+ */
+class SerializedParsedExpression extends ParsedExpression
+{
+ private $nodes;
+
+ /**
+ * @param string $expression An expression
+ * @param string $nodes The serialized nodes for the expression
+ */
+ public function __construct($expression, $nodes)
+ {
+ $this->expression = (string) $expression;
+ $this->nodes = $nodes;
+ }
+
+ public function getNodes()
+ {
+ return unserialize($this->nodes);
+ }
+}
diff --git a/console/skel/symfony/expression-language/SyntaxError.php b/console/skel/symfony/expression-language/SyntaxError.php
new file mode 100644
index 0000000..12348e6
--- /dev/null
+++ b/console/skel/symfony/expression-language/SyntaxError.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage;
+
+class SyntaxError extends \LogicException
+{
+ public function __construct($message, $cursor = 0, $expression = '', $subject = null, array $proposals = null)
+ {
+ $message = sprintf('%s around position %d', $message, $cursor);
+ if ($expression) {
+ $message = sprintf('%s for expression `%s`', $message, $expression);
+ }
+ $message .= '.';
+
+ if (null !== $subject && null !== $proposals) {
+ $minScore = INF;
+ foreach ($proposals as $proposal) {
+ $distance = levenshtein($subject, $proposal);
+ if ($distance < $minScore) {
+ $guess = $proposal;
+ $minScore = $distance;
+ }
+ }
+
+ if (isset($guess) && $minScore < 3) {
+ $message .= sprintf(' Did you mean "%s"?', $guess);
+ }
+ }
+
+ parent::__construct($message);
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/ExpressionFunctionTest.php b/console/skel/symfony/expression-language/Tests/ExpressionFunctionTest.php
new file mode 100644
index 0000000..d7e23cb
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/ExpressionFunctionTest.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ExpressionLanguage\ExpressionFunction;
+
+/**
+ * Tests ExpressionFunction.
+ *
+ * @author Dany Maillard
+ */
+class ExpressionFunctionTest extends TestCase
+{
+ public function testFunctionDoesNotExist()
+ {
+ $this->expectException('InvalidArgumentException');
+ $this->expectExceptionMessage('PHP function "fn_does_not_exist" does not exist.');
+ ExpressionFunction::fromPhp('fn_does_not_exist');
+ }
+
+ public function testFunctionNamespaced()
+ {
+ $this->expectException('InvalidArgumentException');
+ $this->expectExceptionMessage('An expression function name must be defined when PHP function "Symfony\Component\ExpressionLanguage\Tests\fn_namespaced" is namespaced.');
+ ExpressionFunction::fromPhp('Symfony\Component\ExpressionLanguage\Tests\fn_namespaced');
+ }
+}
+
+function fn_namespaced()
+{
+}
diff --git a/console/skel/symfony/expression-language/Tests/ExpressionLanguageTest.php b/console/skel/symfony/expression-language/Tests/ExpressionLanguageTest.php
new file mode 100644
index 0000000..fc2f80e
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/ExpressionLanguageTest.php
@@ -0,0 +1,308 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ExpressionLanguage\ExpressionFunction;
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\ExpressionLanguage\ParsedExpression;
+use Symfony\Component\ExpressionLanguage\Tests\Fixtures\TestProvider;
+
+class ExpressionLanguageTest extends TestCase
+{
+ public function testCachedParse()
+ {
+ $cacheMock = $this->getMockBuilder('Psr\Cache\CacheItemPoolInterface')->getMock();
+ $cacheItemMock = $this->getMockBuilder('Psr\Cache\CacheItemInterface')->getMock();
+ $savedParsedExpression = null;
+ $expressionLanguage = new ExpressionLanguage($cacheMock);
+
+ $cacheMock
+ ->expects($this->exactly(2))
+ ->method('getItem')
+ ->with('1%20%2B%201%2F%2F')
+ ->willReturn($cacheItemMock)
+ ;
+
+ $cacheItemMock
+ ->expects($this->exactly(2))
+ ->method('get')
+ ->willReturnCallback(function () use (&$savedParsedExpression) {
+ return $savedParsedExpression;
+ })
+ ;
+
+ $cacheItemMock
+ ->expects($this->exactly(1))
+ ->method('set')
+ ->with($this->isInstanceOf(ParsedExpression::class))
+ ->willReturnCallback(function ($parsedExpression) use (&$savedParsedExpression) {
+ $savedParsedExpression = $parsedExpression;
+ })
+ ;
+
+ $cacheMock
+ ->expects($this->exactly(1))
+ ->method('save')
+ ->with($cacheItemMock)
+ ;
+
+ $parsedExpression = $expressionLanguage->parse('1 + 1', []);
+ $this->assertSame($savedParsedExpression, $parsedExpression);
+
+ $parsedExpression = $expressionLanguage->parse('1 + 1', []);
+ $this->assertSame($savedParsedExpression, $parsedExpression);
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testCachedParseWithDeprecatedParserCacheInterface()
+ {
+ $cacheMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock();
+
+ $savedParsedExpression = null;
+ $expressionLanguage = new ExpressionLanguage($cacheMock);
+
+ $cacheMock
+ ->expects($this->exactly(1))
+ ->method('fetch')
+ ->with('1%20%2B%201%2F%2F')
+ ->willReturn($savedParsedExpression)
+ ;
+
+ $cacheMock
+ ->expects($this->exactly(1))
+ ->method('save')
+ ->with('1%20%2B%201%2F%2F', $this->isInstanceOf(ParsedExpression::class))
+ ->willReturnCallback(function ($key, $expression) use (&$savedParsedExpression) {
+ $savedParsedExpression = $expression;
+ })
+ ;
+
+ $parsedExpression = $expressionLanguage->parse('1 + 1', []);
+ $this->assertSame($savedParsedExpression, $parsedExpression);
+ }
+
+ public function testWrongCacheImplementation()
+ {
+ $this->expectException('InvalidArgumentException');
+ $this->expectExceptionMessage('Cache argument has to implement Psr\Cache\CacheItemPoolInterface.');
+ $cacheMock = $this->getMockBuilder('Psr\Cache\CacheItemSpoolInterface')->getMock();
+ new ExpressionLanguage($cacheMock);
+ }
+
+ public function testConstantFunction()
+ {
+ $expressionLanguage = new ExpressionLanguage();
+ $this->assertEquals(PHP_VERSION, $expressionLanguage->evaluate('constant("PHP_VERSION")'));
+
+ $expressionLanguage = new ExpressionLanguage();
+ $this->assertEquals('\constant("PHP_VERSION")', $expressionLanguage->compile('constant("PHP_VERSION")'));
+ }
+
+ public function testProviders()
+ {
+ $expressionLanguage = new ExpressionLanguage(null, [new TestProvider()]);
+ $this->assertEquals('foo', $expressionLanguage->evaluate('identity("foo")'));
+ $this->assertEquals('"foo"', $expressionLanguage->compile('identity("foo")'));
+ $this->assertEquals('FOO', $expressionLanguage->evaluate('strtoupper("foo")'));
+ $this->assertEquals('\strtoupper("foo")', $expressionLanguage->compile('strtoupper("foo")'));
+ $this->assertEquals('foo', $expressionLanguage->evaluate('strtolower("FOO")'));
+ $this->assertEquals('\strtolower("FOO")', $expressionLanguage->compile('strtolower("FOO")'));
+ $this->assertTrue($expressionLanguage->evaluate('fn_namespaced()'));
+ $this->assertEquals('\Symfony\Component\ExpressionLanguage\Tests\Fixtures\fn_namespaced()', $expressionLanguage->compile('fn_namespaced()'));
+ }
+
+ /**
+ * @dataProvider shortCircuitProviderEvaluate
+ */
+ public function testShortCircuitOperatorsEvaluate($expression, array $values, $expected)
+ {
+ $expressionLanguage = new ExpressionLanguage();
+ $this->assertEquals($expected, $expressionLanguage->evaluate($expression, $values));
+ }
+
+ /**
+ * @dataProvider shortCircuitProviderCompile
+ */
+ public function testShortCircuitOperatorsCompile($expression, array $names, $expected)
+ {
+ $result = null;
+ $expressionLanguage = new ExpressionLanguage();
+ eval(sprintf('$result = %s;', $expressionLanguage->compile($expression, $names)));
+ $this->assertSame($expected, $result);
+ }
+
+ public function testParseThrowsInsteadOfNotice()
+ {
+ $this->expectException('Symfony\Component\ExpressionLanguage\SyntaxError');
+ $this->expectExceptionMessage('Unexpected end of expression around position 6 for expression `node.`.');
+ $expressionLanguage = new ExpressionLanguage();
+ $expressionLanguage->parse('node.', ['node']);
+ }
+
+ public function shortCircuitProviderEvaluate()
+ {
+ $object = $this->getMockBuilder('stdClass')->setMethods(['foo'])->getMock();
+ $object->expects($this->never())->method('foo');
+
+ return [
+ ['false and object.foo()', ['object' => $object], false],
+ ['false && object.foo()', ['object' => $object], false],
+ ['true || object.foo()', ['object' => $object], true],
+ ['true or object.foo()', ['object' => $object], true],
+ ];
+ }
+
+ public function shortCircuitProviderCompile()
+ {
+ return [
+ ['false and foo', ['foo' => 'foo'], false],
+ ['false && foo', ['foo' => 'foo'], false],
+ ['true || foo', ['foo' => 'foo'], true],
+ ['true or foo', ['foo' => 'foo'], true],
+ ];
+ }
+
+ public function testCachingForOverriddenVariableNames()
+ {
+ $expressionLanguage = new ExpressionLanguage();
+ $expression = 'a + b';
+ $expressionLanguage->evaluate($expression, ['a' => 1, 'b' => 1]);
+ $result = $expressionLanguage->compile($expression, ['a', 'B' => 'b']);
+ $this->assertSame('($a + $B)', $result);
+ }
+
+ public function testStrictEquality()
+ {
+ $expressionLanguage = new ExpressionLanguage();
+ $expression = '123 === a';
+ $result = $expressionLanguage->compile($expression, ['a']);
+ $this->assertSame('(123 === $a)', $result);
+ }
+
+ public function testCachingWithDifferentNamesOrder()
+ {
+ $cacheMock = $this->getMockBuilder('Psr\Cache\CacheItemPoolInterface')->getMock();
+ $cacheItemMock = $this->getMockBuilder('Psr\Cache\CacheItemInterface')->getMock();
+ $expressionLanguage = new ExpressionLanguage($cacheMock);
+ $savedParsedExpression = null;
+
+ $cacheMock
+ ->expects($this->exactly(2))
+ ->method('getItem')
+ ->with('a%20%2B%20b%2F%2Fa%7CB%3Ab')
+ ->willReturn($cacheItemMock)
+ ;
+
+ $cacheItemMock
+ ->expects($this->exactly(2))
+ ->method('get')
+ ->willReturnCallback(function () use (&$savedParsedExpression) {
+ return $savedParsedExpression;
+ })
+ ;
+
+ $cacheItemMock
+ ->expects($this->exactly(1))
+ ->method('set')
+ ->with($this->isInstanceOf(ParsedExpression::class))
+ ->willReturnCallback(function ($parsedExpression) use (&$savedParsedExpression) {
+ $savedParsedExpression = $parsedExpression;
+ })
+ ;
+
+ $cacheMock
+ ->expects($this->exactly(1))
+ ->method('save')
+ ->with($cacheItemMock)
+ ;
+
+ $expression = 'a + b';
+ $expressionLanguage->compile($expression, ['a', 'B' => 'b']);
+ $expressionLanguage->compile($expression, ['B' => 'b', 'a']);
+ }
+
+ public function testOperatorCollisions()
+ {
+ $expressionLanguage = new ExpressionLanguage();
+ $expression = 'foo.not in [bar]';
+ $compiled = $expressionLanguage->compile($expression, ['foo', 'bar']);
+ $this->assertSame('in_array($foo->not, [0 => $bar])', $compiled);
+
+ $result = $expressionLanguage->evaluate($expression, ['foo' => (object) ['not' => 'test'], 'bar' => 'test']);
+ $this->assertTrue($result);
+ }
+
+ /**
+ * @dataProvider getRegisterCallbacks
+ */
+ public function testRegisterAfterParse($registerCallback)
+ {
+ $this->expectException('LogicException');
+ $el = new ExpressionLanguage();
+ $el->parse('1 + 1', []);
+ $registerCallback($el);
+ }
+
+ /**
+ * @dataProvider getRegisterCallbacks
+ */
+ public function testRegisterAfterEval($registerCallback)
+ {
+ $this->expectException('LogicException');
+ $el = new ExpressionLanguage();
+ $el->evaluate('1 + 1');
+ $registerCallback($el);
+ }
+
+ public function testCallBadCallable()
+ {
+ $this->expectException('RuntimeException');
+ $this->expectExceptionMessageRegExp('/Unable to call method "\w+" of object "\w+"./');
+ $el = new ExpressionLanguage();
+ $el->evaluate('foo.myfunction()', ['foo' => new \stdClass()]);
+ }
+
+ /**
+ * @dataProvider getRegisterCallbacks
+ */
+ public function testRegisterAfterCompile($registerCallback)
+ {
+ $this->expectException('LogicException');
+ $el = new ExpressionLanguage();
+ $el->compile('1 + 1');
+ $registerCallback($el);
+ }
+
+ public function getRegisterCallbacks()
+ {
+ return [
+ [
+ function (ExpressionLanguage $el) {
+ $el->register('fn', function () {}, function () {});
+ },
+ ],
+ [
+ function (ExpressionLanguage $el) {
+ $el->addFunction(new ExpressionFunction('fn', function () {}, function () {}));
+ },
+ ],
+ [
+ function (ExpressionLanguage $el) {
+ $el->registerProvider(new TestProvider());
+ },
+ ],
+ ];
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/ExpressionTest.php b/console/skel/symfony/expression-language/Tests/ExpressionTest.php
new file mode 100644
index 0000000..052ef22
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/ExpressionTest.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ExpressionLanguage\Expression;
+
+class ExpressionTest extends TestCase
+{
+ public function testSerialization()
+ {
+ $expression = new Expression('kernel.boot()');
+
+ $serializedExpression = serialize($expression);
+ $unserializedExpression = unserialize($serializedExpression);
+
+ $this->assertEquals($expression, $unserializedExpression);
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/Fixtures/TestProvider.php b/console/skel/symfony/expression-language/Tests/Fixtures/TestProvider.php
new file mode 100644
index 0000000..8405d4f
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/Fixtures/TestProvider.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests\Fixtures;
+
+use Symfony\Component\ExpressionLanguage\ExpressionFunction;
+use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
+use Symfony\Component\ExpressionLanguage\ExpressionPhpFunction;
+
+class TestProvider implements ExpressionFunctionProviderInterface
+{
+ public function getFunctions()
+ {
+ return [
+ new ExpressionFunction('identity', function ($input) {
+ return $input;
+ }, function (array $values, $input) {
+ return $input;
+ }),
+
+ ExpressionFunction::fromPhp('strtoupper'),
+
+ ExpressionFunction::fromPhp('\strtolower'),
+
+ ExpressionFunction::fromPhp('Symfony\Component\ExpressionLanguage\Tests\Fixtures\fn_namespaced', 'fn_namespaced'),
+ ];
+ }
+}
+
+function fn_namespaced()
+{
+ return true;
+}
diff --git a/console/skel/symfony/expression-language/Tests/LexerTest.php b/console/skel/symfony/expression-language/Tests/LexerTest.php
new file mode 100644
index 0000000..6c3d1a7
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/LexerTest.php
@@ -0,0 +1,129 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ExpressionLanguage\Lexer;
+use Symfony\Component\ExpressionLanguage\Token;
+use Symfony\Component\ExpressionLanguage\TokenStream;
+
+class LexerTest extends TestCase
+{
+ /**
+ * @var Lexer
+ */
+ private $lexer;
+
+ protected function setUp()
+ {
+ $this->lexer = new Lexer();
+ }
+
+ /**
+ * @dataProvider getTokenizeData
+ */
+ public function testTokenize($tokens, $expression)
+ {
+ $tokens[] = new Token('end of expression', null, \strlen($expression) + 1);
+ $this->assertEquals(new TokenStream($tokens, $expression), $this->lexer->tokenize($expression));
+ }
+
+ public function testTokenizeThrowsErrorWithMessage()
+ {
+ $this->expectException('Symfony\Component\ExpressionLanguage\SyntaxError');
+ $this->expectExceptionMessage('Unexpected character "\'" around position 33 for expression `service(faulty.expression.example\').dummyMethod()`.');
+ $expression = "service(faulty.expression.example').dummyMethod()";
+ $this->lexer->tokenize($expression);
+ }
+
+ public function testTokenizeThrowsErrorOnUnclosedBrace()
+ {
+ $this->expectException('Symfony\Component\ExpressionLanguage\SyntaxError');
+ $this->expectExceptionMessage('Unclosed "(" around position 7 for expression `service(unclosed.expression.dummyMethod()`.');
+ $expression = 'service(unclosed.expression.dummyMethod()';
+ $this->lexer->tokenize($expression);
+ }
+
+ public function getTokenizeData()
+ {
+ return [
+ [
+ [new Token('name', 'a', 3)],
+ ' a ',
+ ],
+ [
+ [new Token('name', 'a', 1)],
+ 'a',
+ ],
+ [
+ [new Token('string', 'foo', 1)],
+ '"foo"',
+ ],
+ [
+ [new Token('number', '3', 1)],
+ '3',
+ ],
+ [
+ [new Token('operator', '+', 1)],
+ '+',
+ ],
+ [
+ [new Token('punctuation', '.', 1)],
+ '.',
+ ],
+ [
+ [
+ new Token('punctuation', '(', 1),
+ new Token('number', '3', 2),
+ new Token('operator', '+', 4),
+ new Token('number', '5', 6),
+ new Token('punctuation', ')', 7),
+ new Token('operator', '~', 9),
+ new Token('name', 'foo', 11),
+ new Token('punctuation', '(', 14),
+ new Token('string', 'bar', 15),
+ new Token('punctuation', ')', 20),
+ new Token('punctuation', '.', 21),
+ new Token('name', 'baz', 22),
+ new Token('punctuation', '[', 25),
+ new Token('number', '4', 26),
+ new Token('punctuation', ']', 27),
+ ],
+ '(3 + 5) ~ foo("bar").baz[4]',
+ ],
+ [
+ [new Token('operator', '..', 1)],
+ '..',
+ ],
+ [
+ [new Token('string', '#foo', 1)],
+ "'#foo'",
+ ],
+ [
+ [new Token('string', '#foo', 1)],
+ '"#foo"',
+ ],
+ [
+ [
+ new Token('name', 'foo', 1),
+ new Token('punctuation', '.', 4),
+ new Token('name', 'not', 5),
+ new Token('operator', 'in', 9),
+ new Token('punctuation', '[', 12),
+ new Token('name', 'bar', 13),
+ new Token('punctuation', ']', 16),
+ ],
+ 'foo.not in [bar]',
+ ],
+ ];
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/Node/AbstractNodeTest.php b/console/skel/symfony/expression-language/Tests/Node/AbstractNodeTest.php
new file mode 100644
index 0000000..2975037
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/Node/AbstractNodeTest.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests\Node;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ExpressionLanguage\Compiler;
+
+abstract class AbstractNodeTest extends TestCase
+{
+ /**
+ * @dataProvider getEvaluateData
+ */
+ public function testEvaluate($expected, $node, $variables = [], $functions = [])
+ {
+ $this->assertSame($expected, $node->evaluate($functions, $variables));
+ }
+
+ abstract public function getEvaluateData();
+
+ /**
+ * @dataProvider getCompileData
+ */
+ public function testCompile($expected, $node, $functions = [])
+ {
+ $compiler = new Compiler($functions);
+ $node->compile($compiler);
+ $this->assertSame($expected, $compiler->getSource());
+ }
+
+ abstract public function getCompileData();
+
+ /**
+ * @dataProvider getDumpData
+ */
+ public function testDump($expected, $node)
+ {
+ $this->assertSame($expected, $node->dump());
+ }
+
+ abstract public function getDumpData();
+}
diff --git a/console/skel/symfony/expression-language/Tests/Node/ArgumentsNodeTest.php b/console/skel/symfony/expression-language/Tests/Node/ArgumentsNodeTest.php
new file mode 100644
index 0000000..9d88b4a
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/Node/ArgumentsNodeTest.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests\Node;
+
+use Symfony\Component\ExpressionLanguage\Node\ArgumentsNode;
+
+class ArgumentsNodeTest extends ArrayNodeTest
+{
+ public function getCompileData()
+ {
+ return [
+ ['"a", "b"', $this->getArrayNode()],
+ ];
+ }
+
+ public function getDumpData()
+ {
+ return [
+ ['"a", "b"', $this->getArrayNode()],
+ ];
+ }
+
+ protected function createArrayNode()
+ {
+ return new ArgumentsNode();
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/Node/ArrayNodeTest.php b/console/skel/symfony/expression-language/Tests/Node/ArrayNodeTest.php
new file mode 100644
index 0000000..3d03d83
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/Node/ArrayNodeTest.php
@@ -0,0 +1,73 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests\Node;
+
+use Symfony\Component\ExpressionLanguage\Node\ArrayNode;
+use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
+
+class ArrayNodeTest extends AbstractNodeTest
+{
+ public function testSerialization()
+ {
+ $node = $this->createArrayNode();
+ $node->addElement(new ConstantNode('foo'));
+
+ $serializedNode = serialize($node);
+ $unserializedNode = unserialize($serializedNode);
+
+ $this->assertEquals($node, $unserializedNode);
+ $this->assertNotEquals($this->createArrayNode(), $unserializedNode);
+ }
+
+ public function getEvaluateData()
+ {
+ return [
+ [['b' => 'a', 'b'], $this->getArrayNode()],
+ ];
+ }
+
+ public function getCompileData()
+ {
+ return [
+ ['["b" => "a", 0 => "b"]', $this->getArrayNode()],
+ ];
+ }
+
+ public function getDumpData()
+ {
+ yield ['{"b": "a", 0: "b"}', $this->getArrayNode()];
+
+ $array = $this->createArrayNode();
+ $array->addElement(new ConstantNode('c'), new ConstantNode('a"b'));
+ $array->addElement(new ConstantNode('d'), new ConstantNode('a\b'));
+ yield ['{"a\\"b": "c", "a\\\\b": "d"}', $array];
+
+ $array = $this->createArrayNode();
+ $array->addElement(new ConstantNode('c'));
+ $array->addElement(new ConstantNode('d'));
+ yield ['["c", "d"]', $array];
+ }
+
+ protected function getArrayNode()
+ {
+ $array = $this->createArrayNode();
+ $array->addElement(new ConstantNode('a'), new ConstantNode('b'));
+ $array->addElement(new ConstantNode('b'));
+
+ return $array;
+ }
+
+ protected function createArrayNode()
+ {
+ return new ArrayNode();
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/Node/BinaryNodeTest.php b/console/skel/symfony/expression-language/Tests/Node/BinaryNodeTest.php
new file mode 100644
index 0000000..b45a1e5
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/Node/BinaryNodeTest.php
@@ -0,0 +1,166 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests\Node;
+
+use Symfony\Component\ExpressionLanguage\Node\ArrayNode;
+use Symfony\Component\ExpressionLanguage\Node\BinaryNode;
+use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
+
+class BinaryNodeTest extends AbstractNodeTest
+{
+ public function getEvaluateData()
+ {
+ $array = new ArrayNode();
+ $array->addElement(new ConstantNode('a'));
+ $array->addElement(new ConstantNode('b'));
+
+ return [
+ [true, new BinaryNode('or', new ConstantNode(true), new ConstantNode(false))],
+ [true, new BinaryNode('||', new ConstantNode(true), new ConstantNode(false))],
+ [false, new BinaryNode('and', new ConstantNode(true), new ConstantNode(false))],
+ [false, new BinaryNode('&&', new ConstantNode(true), new ConstantNode(false))],
+
+ [0, new BinaryNode('&', new ConstantNode(2), new ConstantNode(4))],
+ [6, new BinaryNode('|', new ConstantNode(2), new ConstantNode(4))],
+ [6, new BinaryNode('^', new ConstantNode(2), new ConstantNode(4))],
+
+ [true, new BinaryNode('<', new ConstantNode(1), new ConstantNode(2))],
+ [true, new BinaryNode('<=', new ConstantNode(1), new ConstantNode(2))],
+ [true, new BinaryNode('<=', new ConstantNode(1), new ConstantNode(1))],
+
+ [false, new BinaryNode('>', new ConstantNode(1), new ConstantNode(2))],
+ [false, new BinaryNode('>=', new ConstantNode(1), new ConstantNode(2))],
+ [true, new BinaryNode('>=', new ConstantNode(1), new ConstantNode(1))],
+
+ [true, new BinaryNode('===', new ConstantNode(true), new ConstantNode(true))],
+ [false, new BinaryNode('!==', new ConstantNode(true), new ConstantNode(true))],
+
+ [false, new BinaryNode('==', new ConstantNode(2), new ConstantNode(1))],
+ [true, new BinaryNode('!=', new ConstantNode(2), new ConstantNode(1))],
+
+ [-1, new BinaryNode('-', new ConstantNode(1), new ConstantNode(2))],
+ [3, new BinaryNode('+', new ConstantNode(1), new ConstantNode(2))],
+ [4, new BinaryNode('*', new ConstantNode(2), new ConstantNode(2))],
+ [1, new BinaryNode('/', new ConstantNode(2), new ConstantNode(2))],
+ [1, new BinaryNode('%', new ConstantNode(5), new ConstantNode(2))],
+ [25, new BinaryNode('**', new ConstantNode(5), new ConstantNode(2))],
+ ['ab', new BinaryNode('~', new ConstantNode('a'), new ConstantNode('b'))],
+
+ [true, new BinaryNode('in', new ConstantNode('a'), $array)],
+ [false, new BinaryNode('in', new ConstantNode('c'), $array)],
+ [true, new BinaryNode('not in', new ConstantNode('c'), $array)],
+ [false, new BinaryNode('not in', new ConstantNode('a'), $array)],
+
+ [[1, 2, 3], new BinaryNode('..', new ConstantNode(1), new ConstantNode(3))],
+
+ [1, new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+$/'))],
+ ];
+ }
+
+ public function getCompileData()
+ {
+ $array = new ArrayNode();
+ $array->addElement(new ConstantNode('a'));
+ $array->addElement(new ConstantNode('b'));
+
+ return [
+ ['(true || false)', new BinaryNode('or', new ConstantNode(true), new ConstantNode(false))],
+ ['(true || false)', new BinaryNode('||', new ConstantNode(true), new ConstantNode(false))],
+ ['(true && false)', new BinaryNode('and', new ConstantNode(true), new ConstantNode(false))],
+ ['(true && false)', new BinaryNode('&&', new ConstantNode(true), new ConstantNode(false))],
+
+ ['(2 & 4)', new BinaryNode('&', new ConstantNode(2), new ConstantNode(4))],
+ ['(2 | 4)', new BinaryNode('|', new ConstantNode(2), new ConstantNode(4))],
+ ['(2 ^ 4)', new BinaryNode('^', new ConstantNode(2), new ConstantNode(4))],
+
+ ['(1 < 2)', new BinaryNode('<', new ConstantNode(1), new ConstantNode(2))],
+ ['(1 <= 2)', new BinaryNode('<=', new ConstantNode(1), new ConstantNode(2))],
+ ['(1 <= 1)', new BinaryNode('<=', new ConstantNode(1), new ConstantNode(1))],
+
+ ['(1 > 2)', new BinaryNode('>', new ConstantNode(1), new ConstantNode(2))],
+ ['(1 >= 2)', new BinaryNode('>=', new ConstantNode(1), new ConstantNode(2))],
+ ['(1 >= 1)', new BinaryNode('>=', new ConstantNode(1), new ConstantNode(1))],
+
+ ['(true === true)', new BinaryNode('===', new ConstantNode(true), new ConstantNode(true))],
+ ['(true !== true)', new BinaryNode('!==', new ConstantNode(true), new ConstantNode(true))],
+
+ ['(2 == 1)', new BinaryNode('==', new ConstantNode(2), new ConstantNode(1))],
+ ['(2 != 1)', new BinaryNode('!=', new ConstantNode(2), new ConstantNode(1))],
+
+ ['(1 - 2)', new BinaryNode('-', new ConstantNode(1), new ConstantNode(2))],
+ ['(1 + 2)', new BinaryNode('+', new ConstantNode(1), new ConstantNode(2))],
+ ['(2 * 2)', new BinaryNode('*', new ConstantNode(2), new ConstantNode(2))],
+ ['(2 / 2)', new BinaryNode('/', new ConstantNode(2), new ConstantNode(2))],
+ ['(5 % 2)', new BinaryNode('%', new ConstantNode(5), new ConstantNode(2))],
+ ['pow(5, 2)', new BinaryNode('**', new ConstantNode(5), new ConstantNode(2))],
+ ['("a" . "b")', new BinaryNode('~', new ConstantNode('a'), new ConstantNode('b'))],
+
+ ['in_array("a", [0 => "a", 1 => "b"])', new BinaryNode('in', new ConstantNode('a'), $array)],
+ ['in_array("c", [0 => "a", 1 => "b"])', new BinaryNode('in', new ConstantNode('c'), $array)],
+ ['!in_array("c", [0 => "a", 1 => "b"])', new BinaryNode('not in', new ConstantNode('c'), $array)],
+ ['!in_array("a", [0 => "a", 1 => "b"])', new BinaryNode('not in', new ConstantNode('a'), $array)],
+
+ ['range(1, 3)', new BinaryNode('..', new ConstantNode(1), new ConstantNode(3))],
+
+ ['preg_match("/^[a-z]+/i\$/", "abc")', new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+/i$/'))],
+ ];
+ }
+
+ public function getDumpData()
+ {
+ $array = new ArrayNode();
+ $array->addElement(new ConstantNode('a'));
+ $array->addElement(new ConstantNode('b'));
+
+ return [
+ ['(true or false)', new BinaryNode('or', new ConstantNode(true), new ConstantNode(false))],
+ ['(true || false)', new BinaryNode('||', new ConstantNode(true), new ConstantNode(false))],
+ ['(true and false)', new BinaryNode('and', new ConstantNode(true), new ConstantNode(false))],
+ ['(true && false)', new BinaryNode('&&', new ConstantNode(true), new ConstantNode(false))],
+
+ ['(2 & 4)', new BinaryNode('&', new ConstantNode(2), new ConstantNode(4))],
+ ['(2 | 4)', new BinaryNode('|', new ConstantNode(2), new ConstantNode(4))],
+ ['(2 ^ 4)', new BinaryNode('^', new ConstantNode(2), new ConstantNode(4))],
+
+ ['(1 < 2)', new BinaryNode('<', new ConstantNode(1), new ConstantNode(2))],
+ ['(1 <= 2)', new BinaryNode('<=', new ConstantNode(1), new ConstantNode(2))],
+ ['(1 <= 1)', new BinaryNode('<=', new ConstantNode(1), new ConstantNode(1))],
+
+ ['(1 > 2)', new BinaryNode('>', new ConstantNode(1), new ConstantNode(2))],
+ ['(1 >= 2)', new BinaryNode('>=', new ConstantNode(1), new ConstantNode(2))],
+ ['(1 >= 1)', new BinaryNode('>=', new ConstantNode(1), new ConstantNode(1))],
+
+ ['(true === true)', new BinaryNode('===', new ConstantNode(true), new ConstantNode(true))],
+ ['(true !== true)', new BinaryNode('!==', new ConstantNode(true), new ConstantNode(true))],
+
+ ['(2 == 1)', new BinaryNode('==', new ConstantNode(2), new ConstantNode(1))],
+ ['(2 != 1)', new BinaryNode('!=', new ConstantNode(2), new ConstantNode(1))],
+
+ ['(1 - 2)', new BinaryNode('-', new ConstantNode(1), new ConstantNode(2))],
+ ['(1 + 2)', new BinaryNode('+', new ConstantNode(1), new ConstantNode(2))],
+ ['(2 * 2)', new BinaryNode('*', new ConstantNode(2), new ConstantNode(2))],
+ ['(2 / 2)', new BinaryNode('/', new ConstantNode(2), new ConstantNode(2))],
+ ['(5 % 2)', new BinaryNode('%', new ConstantNode(5), new ConstantNode(2))],
+ ['(5 ** 2)', new BinaryNode('**', new ConstantNode(5), new ConstantNode(2))],
+ ['("a" ~ "b")', new BinaryNode('~', new ConstantNode('a'), new ConstantNode('b'))],
+
+ ['("a" in ["a", "b"])', new BinaryNode('in', new ConstantNode('a'), $array)],
+ ['("c" in ["a", "b"])', new BinaryNode('in', new ConstantNode('c'), $array)],
+ ['("c" not in ["a", "b"])', new BinaryNode('not in', new ConstantNode('c'), $array)],
+ ['("a" not in ["a", "b"])', new BinaryNode('not in', new ConstantNode('a'), $array)],
+
+ ['(1 .. 3)', new BinaryNode('..', new ConstantNode(1), new ConstantNode(3))],
+
+ ['("abc" matches "/^[a-z]+/i$/")', new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+/i$/'))],
+ ];
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/Node/ConditionalNodeTest.php b/console/skel/symfony/expression-language/Tests/Node/ConditionalNodeTest.php
new file mode 100644
index 0000000..13b7cbd
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/Node/ConditionalNodeTest.php
@@ -0,0 +1,42 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests\Node;
+
+use Symfony\Component\ExpressionLanguage\Node\ConditionalNode;
+use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
+
+class ConditionalNodeTest extends AbstractNodeTest
+{
+ public function getEvaluateData()
+ {
+ return [
+ [1, new ConditionalNode(new ConstantNode(true), new ConstantNode(1), new ConstantNode(2))],
+ [2, new ConditionalNode(new ConstantNode(false), new ConstantNode(1), new ConstantNode(2))],
+ ];
+ }
+
+ public function getCompileData()
+ {
+ return [
+ ['((true) ? (1) : (2))', new ConditionalNode(new ConstantNode(true), new ConstantNode(1), new ConstantNode(2))],
+ ['((false) ? (1) : (2))', new ConditionalNode(new ConstantNode(false), new ConstantNode(1), new ConstantNode(2))],
+ ];
+ }
+
+ public function getDumpData()
+ {
+ return [
+ ['(true ? 1 : 2)', new ConditionalNode(new ConstantNode(true), new ConstantNode(1), new ConstantNode(2))],
+ ['(false ? 1 : 2)', new ConditionalNode(new ConstantNode(false), new ConstantNode(1), new ConstantNode(2))],
+ ];
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/Node/ConstantNodeTest.php b/console/skel/symfony/expression-language/Tests/Node/ConstantNodeTest.php
new file mode 100644
index 0000000..fee9f5b
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/Node/ConstantNodeTest.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests\Node;
+
+use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
+
+class ConstantNodeTest extends AbstractNodeTest
+{
+ public function getEvaluateData()
+ {
+ return [
+ [false, new ConstantNode(false)],
+ [true, new ConstantNode(true)],
+ [null, new ConstantNode(null)],
+ [3, new ConstantNode(3)],
+ [3.3, new ConstantNode(3.3)],
+ ['foo', new ConstantNode('foo')],
+ [[1, 'b' => 'a'], new ConstantNode([1, 'b' => 'a'])],
+ ];
+ }
+
+ public function getCompileData()
+ {
+ return [
+ ['false', new ConstantNode(false)],
+ ['true', new ConstantNode(true)],
+ ['null', new ConstantNode(null)],
+ ['3', new ConstantNode(3)],
+ ['3.3', new ConstantNode(3.3)],
+ ['"foo"', new ConstantNode('foo')],
+ ['[0 => 1, "b" => "a"]', new ConstantNode([1, 'b' => 'a'])],
+ ];
+ }
+
+ public function getDumpData()
+ {
+ return [
+ ['false', new ConstantNode(false)],
+ ['true', new ConstantNode(true)],
+ ['null', new ConstantNode(null)],
+ ['3', new ConstantNode(3)],
+ ['3.3', new ConstantNode(3.3)],
+ ['"foo"', new ConstantNode('foo')],
+ ['foo', new ConstantNode('foo', true)],
+ ['{0: 1, "b": "a", 1: true}', new ConstantNode([1, 'b' => 'a', true])],
+ ['{"a\\"b": "c", "a\\\\b": "d"}', new ConstantNode(['a"b' => 'c', 'a\\b' => 'd'])],
+ ['["c", "d"]', new ConstantNode(['c', 'd'])],
+ ['{"a": ["b"]}', new ConstantNode(['a' => ['b']])],
+ ];
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/Node/FunctionNodeTest.php b/console/skel/symfony/expression-language/Tests/Node/FunctionNodeTest.php
new file mode 100644
index 0000000..c6cb02c
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/Node/FunctionNodeTest.php
@@ -0,0 +1,52 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests\Node;
+
+use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
+use Symfony\Component\ExpressionLanguage\Node\FunctionNode;
+use Symfony\Component\ExpressionLanguage\Node\Node;
+
+class FunctionNodeTest extends AbstractNodeTest
+{
+ public function getEvaluateData()
+ {
+ return [
+ ['bar', new FunctionNode('foo', new Node([new ConstantNode('bar')])), [], ['foo' => $this->getCallables()]],
+ ];
+ }
+
+ public function getCompileData()
+ {
+ return [
+ ['foo("bar")', new FunctionNode('foo', new Node([new ConstantNode('bar')])), ['foo' => $this->getCallables()]],
+ ];
+ }
+
+ public function getDumpData()
+ {
+ return [
+ ['foo("bar")', new FunctionNode('foo', new Node([new ConstantNode('bar')])), ['foo' => $this->getCallables()]],
+ ];
+ }
+
+ protected function getCallables()
+ {
+ return [
+ 'compiler' => function ($arg) {
+ return sprintf('foo(%s)', $arg);
+ },
+ 'evaluator' => function ($variables, $arg) {
+ return $arg;
+ },
+ ];
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/Node/GetAttrNodeTest.php b/console/skel/symfony/expression-language/Tests/Node/GetAttrNodeTest.php
new file mode 100644
index 0000000..c790f33
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/Node/GetAttrNodeTest.php
@@ -0,0 +1,78 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests\Node;
+
+use Symfony\Component\ExpressionLanguage\Node\ArrayNode;
+use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
+use Symfony\Component\ExpressionLanguage\Node\GetAttrNode;
+use Symfony\Component\ExpressionLanguage\Node\NameNode;
+
+class GetAttrNodeTest extends AbstractNodeTest
+{
+ public function getEvaluateData()
+ {
+ return [
+ ['b', new GetAttrNode(new NameNode('foo'), new ConstantNode(0), $this->getArrayNode(), GetAttrNode::ARRAY_CALL), ['foo' => ['b' => 'a', 'b']]],
+ ['a', new GetAttrNode(new NameNode('foo'), new ConstantNode('b'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL), ['foo' => ['b' => 'a', 'b']]],
+
+ ['bar', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::PROPERTY_CALL), ['foo' => new Obj()]],
+
+ ['baz', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::METHOD_CALL), ['foo' => new Obj()]],
+ ['a', new GetAttrNode(new NameNode('foo'), new NameNode('index'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL), ['foo' => ['b' => 'a', 'b'], 'index' => 'b']],
+ ];
+ }
+
+ public function getCompileData()
+ {
+ return [
+ ['$foo[0]', new GetAttrNode(new NameNode('foo'), new ConstantNode(0), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)],
+ ['$foo["b"]', new GetAttrNode(new NameNode('foo'), new ConstantNode('b'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)],
+
+ ['$foo->foo', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::PROPERTY_CALL), ['foo' => new Obj()]],
+
+ ['$foo->foo(["b" => "a", 0 => "b"])', new GetAttrNode(new NameNode('foo'), new ConstantNode('foo'), $this->getArrayNode(), GetAttrNode::METHOD_CALL), ['foo' => new Obj()]],
+ ['$foo[$index]', new GetAttrNode(new NameNode('foo'), new NameNode('index'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)],
+ ];
+ }
+
+ public function getDumpData()
+ {
+ return [
+ ['foo[0]', new GetAttrNode(new NameNode('foo'), new ConstantNode(0), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)],
+ ['foo["b"]', new GetAttrNode(new NameNode('foo'), new ConstantNode('b'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)],
+
+ ['foo.foo', new GetAttrNode(new NameNode('foo'), new NameNode('foo'), $this->getArrayNode(), GetAttrNode::PROPERTY_CALL), ['foo' => new Obj()]],
+
+ ['foo.foo({"b": "a", 0: "b"})', new GetAttrNode(new NameNode('foo'), new NameNode('foo'), $this->getArrayNode(), GetAttrNode::METHOD_CALL), ['foo' => new Obj()]],
+ ['foo[index]', new GetAttrNode(new NameNode('foo'), new NameNode('index'), $this->getArrayNode(), GetAttrNode::ARRAY_CALL)],
+ ];
+ }
+
+ protected function getArrayNode()
+ {
+ $array = new ArrayNode();
+ $array->addElement(new ConstantNode('a'), new ConstantNode('b'));
+ $array->addElement(new ConstantNode('b'));
+
+ return $array;
+ }
+}
+
+class Obj
+{
+ public $foo = 'bar';
+
+ public function foo()
+ {
+ return 'baz';
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/Node/NameNodeTest.php b/console/skel/symfony/expression-language/Tests/Node/NameNodeTest.php
new file mode 100644
index 0000000..a30c27b
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/Node/NameNodeTest.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests\Node;
+
+use Symfony\Component\ExpressionLanguage\Node\NameNode;
+
+class NameNodeTest extends AbstractNodeTest
+{
+ public function getEvaluateData()
+ {
+ return [
+ ['bar', new NameNode('foo'), ['foo' => 'bar']],
+ ];
+ }
+
+ public function getCompileData()
+ {
+ return [
+ ['$foo', new NameNode('foo')],
+ ];
+ }
+
+ public function getDumpData()
+ {
+ return [
+ ['foo', new NameNode('foo')],
+ ];
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/Node/NodeTest.php b/console/skel/symfony/expression-language/Tests/Node/NodeTest.php
new file mode 100644
index 0000000..351da05
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/Node/NodeTest.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests\Node;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
+use Symfony\Component\ExpressionLanguage\Node\Node;
+
+class NodeTest extends TestCase
+{
+ public function testToString()
+ {
+ $node = new Node([new ConstantNode('foo')]);
+
+ $this->assertEquals(<<<'EOF'
+Node(
+ ConstantNode(value: 'foo')
+)
+EOF
+ , (string) $node);
+ }
+
+ public function testSerialization()
+ {
+ $node = new Node(['foo' => 'bar'], ['bar' => 'foo']);
+
+ $serializedNode = serialize($node);
+ $unserializedNode = unserialize($serializedNode);
+
+ $this->assertEquals($node, $unserializedNode);
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/Node/UnaryNodeTest.php b/console/skel/symfony/expression-language/Tests/Node/UnaryNodeTest.php
new file mode 100644
index 0000000..bac75d2
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/Node/UnaryNodeTest.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests\Node;
+
+use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
+use Symfony\Component\ExpressionLanguage\Node\UnaryNode;
+
+class UnaryNodeTest extends AbstractNodeTest
+{
+ public function getEvaluateData()
+ {
+ return [
+ [-1, new UnaryNode('-', new ConstantNode(1))],
+ [3, new UnaryNode('+', new ConstantNode(3))],
+ [false, new UnaryNode('!', new ConstantNode(true))],
+ [false, new UnaryNode('not', new ConstantNode(true))],
+ ];
+ }
+
+ public function getCompileData()
+ {
+ return [
+ ['(-1)', new UnaryNode('-', new ConstantNode(1))],
+ ['(+3)', new UnaryNode('+', new ConstantNode(3))],
+ ['(!true)', new UnaryNode('!', new ConstantNode(true))],
+ ['(!true)', new UnaryNode('not', new ConstantNode(true))],
+ ];
+ }
+
+ public function getDumpData()
+ {
+ return [
+ ['(- 1)', new UnaryNode('-', new ConstantNode(1))],
+ ['(+ 3)', new UnaryNode('+', new ConstantNode(3))],
+ ['(! true)', new UnaryNode('!', new ConstantNode(true))],
+ ['(not true)', new UnaryNode('not', new ConstantNode(true))],
+ ];
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/ParsedExpressionTest.php b/console/skel/symfony/expression-language/Tests/ParsedExpressionTest.php
new file mode 100644
index 0000000..d28101f
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/ParsedExpressionTest.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ExpressionLanguage\Node\ConstantNode;
+use Symfony\Component\ExpressionLanguage\ParsedExpression;
+
+class ParsedExpressionTest extends TestCase
+{
+ public function testSerialization()
+ {
+ $expression = new ParsedExpression('25', new ConstantNode('25'));
+
+ $serializedExpression = serialize($expression);
+ $unserializedExpression = unserialize($serializedExpression);
+
+ $this->assertEquals($expression, $unserializedExpression);
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/ParserCache/ParserCacheAdapterTest.php b/console/skel/symfony/expression-language/Tests/ParserCache/ParserCacheAdapterTest.php
new file mode 100644
index 0000000..2e92633
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/ParserCache/ParserCacheAdapterTest.php
@@ -0,0 +1,140 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ExpressionLanguage\Node\Node;
+use Symfony\Component\ExpressionLanguage\ParsedExpression;
+use Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheAdapter;
+
+/**
+ * @group legacy
+ */
+class ParserCacheAdapterTest extends TestCase
+{
+ public function testGetItem()
+ {
+ $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock();
+
+ $key = 'key';
+ $value = 'value';
+ $parserCacheAdapter = new ParserCacheAdapter($poolMock);
+
+ $poolMock
+ ->expects($this->once())
+ ->method('fetch')
+ ->with($key)
+ ->willReturn($value)
+ ;
+
+ $cacheItem = $parserCacheAdapter->getItem($key);
+
+ $this->assertEquals($cacheItem->get(), $value);
+ $this->assertEquals($cacheItem->isHit(), true);
+ }
+
+ public function testSave()
+ {
+ $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock();
+ $cacheItemMock = $this->getMockBuilder('Psr\Cache\CacheItemInterface')->getMock();
+ $key = 'key';
+ $value = new ParsedExpression('1 + 1', new Node([], []));
+ $parserCacheAdapter = new ParserCacheAdapter($poolMock);
+
+ $poolMock
+ ->expects($this->once())
+ ->method('save')
+ ->with($key, $value)
+ ;
+
+ $cacheItemMock
+ ->expects($this->once())
+ ->method('getKey')
+ ->willReturn($key)
+ ;
+
+ $cacheItemMock
+ ->expects($this->once())
+ ->method('get')
+ ->willReturn($value)
+ ;
+
+ $parserCacheAdapter->save($cacheItemMock);
+ }
+
+ public function testGetItems()
+ {
+ $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock();
+ $parserCacheAdapter = new ParserCacheAdapter($poolMock);
+ $this->expectException(\BadMethodCallException::class);
+
+ $parserCacheAdapter->getItems();
+ }
+
+ public function testHasItem()
+ {
+ $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock();
+ $key = 'key';
+ $parserCacheAdapter = new ParserCacheAdapter($poolMock);
+ $this->expectException(\BadMethodCallException::class);
+
+ $parserCacheAdapter->hasItem($key);
+ }
+
+ public function testClear()
+ {
+ $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock();
+ $parserCacheAdapter = new ParserCacheAdapter($poolMock);
+ $this->expectException(\BadMethodCallException::class);
+
+ $parserCacheAdapter->clear();
+ }
+
+ public function testDeleteItem()
+ {
+ $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock();
+ $key = 'key';
+ $parserCacheAdapter = new ParserCacheAdapter($poolMock);
+ $this->expectException(\BadMethodCallException::class);
+
+ $parserCacheAdapter->deleteItem($key);
+ }
+
+ public function testDeleteItems()
+ {
+ $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock();
+ $keys = ['key'];
+ $parserCacheAdapter = new ParserCacheAdapter($poolMock);
+ $this->expectException(\BadMethodCallException::class);
+
+ $parserCacheAdapter->deleteItems($keys);
+ }
+
+ public function testSaveDeferred()
+ {
+ $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock();
+ $parserCacheAdapter = new ParserCacheAdapter($poolMock);
+ $cacheItemMock = $this->getMockBuilder('Psr\Cache\CacheItemInterface')->getMock();
+ $this->expectException(\BadMethodCallException::class);
+
+ $parserCacheAdapter->saveDeferred($cacheItemMock);
+ }
+
+ public function testCommit()
+ {
+ $poolMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock();
+ $parserCacheAdapter = new ParserCacheAdapter($poolMock);
+ $this->expectException(\BadMethodCallException::class);
+
+ $parserCacheAdapter->commit();
+ }
+}
diff --git a/console/skel/symfony/expression-language/Tests/ParserTest.php b/console/skel/symfony/expression-language/Tests/ParserTest.php
new file mode 100644
index 0000000..2d5a0a6
--- /dev/null
+++ b/console/skel/symfony/expression-language/Tests/ParserTest.php
@@ -0,0 +1,237 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ExpressionLanguage\Lexer;
+use Symfony\Component\ExpressionLanguage\Node;
+use Symfony\Component\ExpressionLanguage\Parser;
+
+class ParserTest extends TestCase
+{
+ public function testParseWithInvalidName()
+ {
+ $this->expectException('Symfony\Component\ExpressionLanguage\SyntaxError');
+ $this->expectExceptionMessage('Variable "foo" is not valid around position 1 for expression `foo`.');
+ $lexer = new Lexer();
+ $parser = new Parser([]);
+ $parser->parse($lexer->tokenize('foo'));
+ }
+
+ public function testParseWithZeroInNames()
+ {
+ $this->expectException('Symfony\Component\ExpressionLanguage\SyntaxError');
+ $this->expectExceptionMessage('Variable "foo" is not valid around position 1 for expression `foo`.');
+ $lexer = new Lexer();
+ $parser = new Parser([]);
+ $parser->parse($lexer->tokenize('foo'), [0]);
+ }
+
+ /**
+ * @dataProvider getParseData
+ */
+ public function testParse($node, $expression, $names = [])
+ {
+ $lexer = new Lexer();
+ $parser = new Parser([]);
+ $this->assertEquals($node, $parser->parse($lexer->tokenize($expression), $names));
+ }
+
+ public function getParseData()
+ {
+ $arguments = new Node\ArgumentsNode();
+ $arguments->addElement(new Node\ConstantNode('arg1'));
+ $arguments->addElement(new Node\ConstantNode(2));
+ $arguments->addElement(new Node\ConstantNode(true));
+
+ $arrayNode = new Node\ArrayNode();
+ $arrayNode->addElement(new Node\NameNode('bar'));
+
+ return [
+ [
+ new Node\NameNode('a'),
+ 'a',
+ ['a'],
+ ],
+ [
+ new Node\ConstantNode('a'),
+ '"a"',
+ ],
+ [
+ new Node\ConstantNode(3),
+ '3',
+ ],
+ [
+ new Node\ConstantNode(false),
+ 'false',
+ ],
+ [
+ new Node\ConstantNode(true),
+ 'true',
+ ],
+ [
+ new Node\ConstantNode(null),
+ 'null',
+ ],
+ [
+ new Node\UnaryNode('-', new Node\ConstantNode(3)),
+ '-3',
+ ],
+ [
+ new Node\BinaryNode('-', new Node\ConstantNode(3), new Node\ConstantNode(3)),
+ '3 - 3',
+ ],
+ [
+ new Node\BinaryNode('*',
+ new Node\BinaryNode('-', new Node\ConstantNode(3), new Node\ConstantNode(3)),
+ new Node\ConstantNode(2)
+ ),
+ '(3 - 3) * 2',
+ ],
+ [
+ new Node\GetAttrNode(new Node\NameNode('foo'), new Node\ConstantNode('bar', true), new Node\ArgumentsNode(), Node\GetAttrNode::PROPERTY_CALL),
+ 'foo.bar',
+ ['foo'],
+ ],
+ [
+ new Node\GetAttrNode(new Node\NameNode('foo'), new Node\ConstantNode('bar', true), new Node\ArgumentsNode(), Node\GetAttrNode::METHOD_CALL),
+ 'foo.bar()',
+ ['foo'],
+ ],
+ [
+ new Node\GetAttrNode(new Node\NameNode('foo'), new Node\ConstantNode('not', true), new Node\ArgumentsNode(), Node\GetAttrNode::METHOD_CALL),
+ 'foo.not()',
+ ['foo'],
+ ],
+ [
+ new Node\GetAttrNode(
+ new Node\NameNode('foo'),
+ new Node\ConstantNode('bar', true),
+ $arguments,
+ Node\GetAttrNode::METHOD_CALL
+ ),
+ 'foo.bar("arg1", 2, true)',
+ ['foo'],
+ ],
+ [
+ new Node\GetAttrNode(new Node\NameNode('foo'), new Node\ConstantNode(3), new Node\ArgumentsNode(), Node\GetAttrNode::ARRAY_CALL),
+ 'foo[3]',
+ ['foo'],
+ ],
+ [
+ new Node\ConditionalNode(new Node\ConstantNode(true), new Node\ConstantNode(true), new Node\ConstantNode(false)),
+ 'true ? true : false',
+ ],
+ [
+ new Node\BinaryNode('matches', new Node\ConstantNode('foo'), new Node\ConstantNode('/foo/')),
+ '"foo" matches "/foo/"',
+ ],
+
+ // chained calls
+ [
+ $this->createGetAttrNode(
+ $this->createGetAttrNode(
+ $this->createGetAttrNode(
+ $this->createGetAttrNode(new Node\NameNode('foo'), 'bar', Node\GetAttrNode::METHOD_CALL),
+ 'foo', Node\GetAttrNode::METHOD_CALL),
+ 'baz', Node\GetAttrNode::PROPERTY_CALL),
+ '3', Node\GetAttrNode::ARRAY_CALL),
+ 'foo.bar().foo().baz[3]',
+ ['foo'],
+ ],
+
+ [
+ new Node\NameNode('foo'),
+ 'bar',
+ ['foo' => 'bar'],
+ ],
+
+ // Operators collisions
+ [
+ new Node\BinaryNode(
+ 'in',
+ new Node\GetAttrNode(
+ new Node\NameNode('foo'),
+ new Node\ConstantNode('not', true),
+ new Node\ArgumentsNode(),
+ Node\GetAttrNode::PROPERTY_CALL
+ ),
+ $arrayNode
+ ),
+ 'foo.not in [bar]',
+ ['foo', 'bar'],
+ ],
+ [
+ new Node\BinaryNode(
+ 'or',
+ new Node\UnaryNode('not', new Node\NameNode('foo')),
+ new Node\GetAttrNode(
+ new Node\NameNode('foo'),
+ new Node\ConstantNode('not', true),
+ new Node\ArgumentsNode(),
+ Node\GetAttrNode::PROPERTY_CALL
+ )
+ ),
+ 'not foo or foo.not',
+ ['foo'],
+ ],
+ ];
+ }
+
+ private function createGetAttrNode($node, $item, $type)
+ {
+ return new Node\GetAttrNode($node, new Node\ConstantNode($item, Node\GetAttrNode::ARRAY_CALL !== $type), new Node\ArgumentsNode(), $type);
+ }
+
+ /**
+ * @dataProvider getInvalidPostfixData
+ */
+ public function testParseWithInvalidPostfixData($expr, $names = [])
+ {
+ $this->expectException('Symfony\Component\ExpressionLanguage\SyntaxError');
+ $lexer = new Lexer();
+ $parser = new Parser([]);
+ $parser->parse($lexer->tokenize($expr), $names);
+ }
+
+ public function getInvalidPostfixData()
+ {
+ return [
+ [
+ 'foo."#"',
+ ['foo'],
+ ],
+ [
+ 'foo."bar"',
+ ['foo'],
+ ],
+ [
+ 'foo.**',
+ ['foo'],
+ ],
+ [
+ 'foo.123',
+ ['foo'],
+ ],
+ ];
+ }
+
+ public function testNameProposal()
+ {
+ $this->expectException('Symfony\Component\ExpressionLanguage\SyntaxError');
+ $this->expectExceptionMessage('Did you mean "baz"?');
+ $lexer = new Lexer();
+ $parser = new Parser([]);
+
+ $parser->parse($lexer->tokenize('foo > bar'), ['foo', 'baz']);
+ }
+}
diff --git a/console/skel/symfony/expression-language/Token.php b/console/skel/symfony/expression-language/Token.php
new file mode 100644
index 0000000..4517335
--- /dev/null
+++ b/console/skel/symfony/expression-language/Token.php
@@ -0,0 +1,66 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage;
+
+/**
+ * Represents a Token.
+ *
+ * @author Fabien Potencier
+ */
+class Token
+{
+ public $value;
+ public $type;
+ public $cursor;
+
+ const EOF_TYPE = 'end of expression';
+ const NAME_TYPE = 'name';
+ const NUMBER_TYPE = 'number';
+ const STRING_TYPE = 'string';
+ const OPERATOR_TYPE = 'operator';
+ const PUNCTUATION_TYPE = 'punctuation';
+
+ /**
+ * @param string $type The type of the token (self::*_TYPE)
+ * @param string|int|float|null $value The token value
+ * @param int $cursor The cursor position in the source
+ */
+ public function __construct($type, $value, $cursor)
+ {
+ $this->type = $type;
+ $this->value = $value;
+ $this->cursor = $cursor;
+ }
+
+ /**
+ * Returns a string representation of the token.
+ *
+ * @return string A string representation of the token
+ */
+ public function __toString()
+ {
+ return sprintf('%3d %-11s %s', $this->cursor, strtoupper($this->type), $this->value);
+ }
+
+ /**
+ * Tests the current token for a type and/or a value.
+ *
+ * @param array|int $type The type to test
+ * @param string|null $value The token value
+ *
+ * @return bool
+ */
+ public function test($type, $value = null)
+ {
+ return $this->type === $type && (null === $value || $this->value == $value);
+ }
+}
diff --git a/console/skel/symfony/expression-language/TokenStream.php b/console/skel/symfony/expression-language/TokenStream.php
new file mode 100644
index 0000000..9096b18
--- /dev/null
+++ b/console/skel/symfony/expression-language/TokenStream.php
@@ -0,0 +1,97 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ExpressionLanguage;
+
+/**
+ * Represents a token stream.
+ *
+ * @author Fabien Potencier
+ */
+class TokenStream
+{
+ public $current;
+
+ private $tokens;
+ private $position = 0;
+ private $expression;
+
+ /**
+ * @param array $tokens An array of tokens
+ * @param string $expression
+ */
+ public function __construct(array $tokens, $expression = '')
+ {
+ $this->tokens = $tokens;
+ $this->current = $tokens[0];
+ $this->expression = $expression;
+ }
+
+ /**
+ * Returns a string representation of the token stream.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return implode("\n", $this->tokens);
+ }
+
+ /**
+ * Sets the pointer to the next token and returns the old one.
+ */
+ public function next()
+ {
+ ++$this->position;
+
+ if (!isset($this->tokens[$this->position])) {
+ throw new SyntaxError('Unexpected end of expression', $this->current->cursor, $this->expression);
+ }
+
+ $this->current = $this->tokens[$this->position];
+ }
+
+ /**
+ * Tests a token.
+ *
+ * @param array|int $type The type to test
+ * @param string|null $value The token value
+ * @param string|null $message The syntax error message
+ */
+ public function expect($type, $value = null, $message = null)
+ {
+ $token = $this->current;
+ if (!$token->test($type, $value)) {
+ throw new SyntaxError(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s)', $message ? $message.'. ' : '', $token->type, $token->value, $type, $value ? sprintf(' with value "%s"', $value) : ''), $token->cursor, $this->expression);
+ }
+ $this->next();
+ }
+
+ /**
+ * Checks if end of stream was reached.
+ *
+ * @return bool
+ */
+ public function isEOF()
+ {
+ return Token::EOF_TYPE === $this->current->type;
+ }
+
+ /**
+ * @internal
+ *
+ * @return string
+ */
+ public function getExpression()
+ {
+ return $this->expression;
+ }
+}
diff --git a/console/skel/symfony/expression-language/composer.json b/console/skel/symfony/expression-language/composer.json
new file mode 100644
index 0000000..ee44185
--- /dev/null
+++ b/console/skel/symfony/expression-language/composer.json
@@ -0,0 +1,35 @@
+{
+ "name": "symfony/expression-language",
+ "type": "library",
+ "description": "Symfony ExpressionLanguage Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "symfony/cache": "~3.1|~4.0",
+ "symfony/polyfill-php70": "~1.6"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\ExpressionLanguage\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ }
+}
diff --git a/console/skel/symfony/expression-language/phpunit.xml.dist b/console/skel/symfony/expression-language/phpunit.xml.dist
new file mode 100644
index 0000000..ae84dcd
--- /dev/null
+++ b/console/skel/symfony/expression-language/phpunit.xml.dist
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/console/skel/symfony/filesystem/.gitignore b/console/skel/symfony/filesystem/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/console/skel/symfony/filesystem/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/console/skel/symfony/filesystem/CHANGELOG.md b/console/skel/symfony/filesystem/CHANGELOG.md
new file mode 100644
index 0000000..d01f5f4
--- /dev/null
+++ b/console/skel/symfony/filesystem/CHANGELOG.md
@@ -0,0 +1,53 @@
+CHANGELOG
+=========
+
+3.4.0
+-----
+
+ * support for passing relative paths to `Filesystem::makePathRelative()` is deprecated and will be removed in 4.0
+
+3.3.0
+-----
+
+ * added `appendToFile()` to append contents to existing files
+
+3.2.0
+-----
+
+ * added `readlink()` as a platform independent method to read links
+
+3.0.0
+-----
+
+ * removed `$mode` argument from `Filesystem::dumpFile()`
+
+2.8.0
+-----
+
+ * added tempnam() a stream aware version of PHP's native tempnam()
+
+2.6.0
+-----
+
+ * added LockHandler
+
+2.3.12
+------
+
+ * deprecated dumpFile() file mode argument.
+
+2.3.0
+-----
+
+ * added the dumpFile() method to atomically write files
+
+2.2.0
+-----
+
+ * added a delete option for the mirror() method
+
+2.1.0
+-----
+
+ * 24eb396 : BC Break : mkdir() function now throws exception in case of failure instead of returning Boolean value
+ * created the component
diff --git a/console/skel/symfony/filesystem/Exception/ExceptionInterface.php b/console/skel/symfony/filesystem/Exception/ExceptionInterface.php
new file mode 100644
index 0000000..8f4f10a
--- /dev/null
+++ b/console/skel/symfony/filesystem/Exception/ExceptionInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Filesystem\Exception;
+
+/**
+ * Exception interface for all exceptions thrown by the component.
+ *
+ * @author Romain Neutron
+ */
+interface ExceptionInterface
+{
+}
diff --git a/console/skel/symfony/filesystem/Exception/FileNotFoundException.php b/console/skel/symfony/filesystem/Exception/FileNotFoundException.php
new file mode 100644
index 0000000..bcc8fe8
--- /dev/null
+++ b/console/skel/symfony/filesystem/Exception/FileNotFoundException.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Filesystem\Exception;
+
+/**
+ * Exception class thrown when a file couldn't be found.
+ *
+ * @author Fabien Potencier
+ * @author Christian Gärtner
+ */
+class FileNotFoundException extends IOException
+{
+ public function __construct($message = null, $code = 0, \Exception $previous = null, $path = null)
+ {
+ if (null === $message) {
+ if (null === $path) {
+ $message = 'File could not be found.';
+ } else {
+ $message = sprintf('File "%s" could not be found.', $path);
+ }
+ }
+
+ parent::__construct($message, $code, $previous, $path);
+ }
+}
diff --git a/console/skel/symfony/filesystem/Exception/IOException.php b/console/skel/symfony/filesystem/Exception/IOException.php
new file mode 100644
index 0000000..144e0e6
--- /dev/null
+++ b/console/skel/symfony/filesystem/Exception/IOException.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Filesystem\Exception;
+
+/**
+ * Exception class thrown when a filesystem operation failure happens.
+ *
+ * @author Romain Neutron
+ * @author Christian Gärtner
+ * @author Fabien Potencier
+ */
+class IOException extends \RuntimeException implements IOExceptionInterface
+{
+ private $path;
+
+ public function __construct($message, $code = 0, \Exception $previous = null, $path = null)
+ {
+ $this->path = $path;
+
+ parent::__construct($message, $code, $previous);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+}
diff --git a/console/skel/symfony/filesystem/Exception/IOExceptionInterface.php b/console/skel/symfony/filesystem/Exception/IOExceptionInterface.php
new file mode 100644
index 0000000..f9d4644
--- /dev/null
+++ b/console/skel/symfony/filesystem/Exception/IOExceptionInterface.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Filesystem\Exception;
+
+/**
+ * IOException interface for file and input/output stream related exceptions thrown by the component.
+ *
+ * @author Christian Gärtner
+ */
+interface IOExceptionInterface extends ExceptionInterface
+{
+ /**
+ * Returns the associated path for the exception.
+ *
+ * @return string|null The path
+ */
+ public function getPath();
+}
diff --git a/console/skel/symfony/filesystem/Filesystem.php b/console/skel/symfony/filesystem/Filesystem.php
new file mode 100644
index 0000000..7d7301c
--- /dev/null
+++ b/console/skel/symfony/filesystem/Filesystem.php
@@ -0,0 +1,775 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Filesystem;
+
+use Symfony\Component\Filesystem\Exception\FileNotFoundException;
+use Symfony\Component\Filesystem\Exception\IOException;
+
+/**
+ * Provides basic utility to manipulate the file system.
+ *
+ * @author Fabien Potencier
+ */
+class Filesystem
+{
+ private static $lastError;
+
+ /**
+ * Copies a file.
+ *
+ * If the target file is older than the origin file, it's always overwritten.
+ * If the target file is newer, it is overwritten only when the
+ * $overwriteNewerFiles option is set to true.
+ *
+ * @param string $originFile The original filename
+ * @param string $targetFile The target filename
+ * @param bool $overwriteNewerFiles If true, target files newer than origin files are overwritten
+ *
+ * @throws FileNotFoundException When originFile doesn't exist
+ * @throws IOException When copy fails
+ */
+ public function copy($originFile, $targetFile, $overwriteNewerFiles = false)
+ {
+ $originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://');
+ if ($originIsLocal && !is_file($originFile)) {
+ throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile);
+ }
+
+ $this->mkdir(\dirname($targetFile));
+
+ $doCopy = true;
+ if (!$overwriteNewerFiles && null === parse_url($originFile, PHP_URL_HOST) && is_file($targetFile)) {
+ $doCopy = filemtime($originFile) > filemtime($targetFile);
+ }
+
+ if ($doCopy) {
+ // https://bugs.php.net/64634
+ if (false === $source = @fopen($originFile, 'r')) {
+ throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile), 0, null, $originFile);
+ }
+
+ // Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
+ if (false === $target = @fopen($targetFile, 'w', null, stream_context_create(['ftp' => ['overwrite' => true]]))) {
+ throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing.', $originFile, $targetFile), 0, null, $originFile);
+ }
+
+ $bytesCopied = stream_copy_to_stream($source, $target);
+ fclose($source);
+ fclose($target);
+ unset($source, $target);
+
+ if (!is_file($targetFile)) {
+ throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile);
+ }
+
+ if ($originIsLocal) {
+ // Like `cp`, preserve executable permission bits
+ @chmod($targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111));
+
+ if ($bytesCopied !== $bytesOrigin = filesize($originFile)) {
+ throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a directory recursively.
+ *
+ * @param string|iterable $dirs The directory path
+ * @param int $mode The directory mode
+ *
+ * @throws IOException On any directory creation failure
+ */
+ public function mkdir($dirs, $mode = 0777)
+ {
+ foreach ($this->toIterable($dirs) as $dir) {
+ if (is_dir($dir)) {
+ continue;
+ }
+
+ if (!self::box('mkdir', $dir, $mode, true)) {
+ if (!is_dir($dir)) {
+ // The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one
+ if (self::$lastError) {
+ throw new IOException(sprintf('Failed to create "%s": %s.', $dir, self::$lastError), 0, null, $dir);
+ }
+ throw new IOException(sprintf('Failed to create "%s"', $dir), 0, null, $dir);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks the existence of files or directories.
+ *
+ * @param string|iterable $files A filename, an array of files, or a \Traversable instance to check
+ *
+ * @return bool true if the file exists, false otherwise
+ */
+ public function exists($files)
+ {
+ $maxPathLength = PHP_MAXPATHLEN - 2;
+
+ foreach ($this->toIterable($files) as $file) {
+ if (\strlen($file) > $maxPathLength) {
+ throw new IOException(sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file);
+ }
+
+ if (!file_exists($file)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Sets access and modification time of file.
+ *
+ * @param string|iterable $files A filename, an array of files, or a \Traversable instance to create
+ * @param int|null $time The touch time as a Unix timestamp, if not supplied the current system time is used
+ * @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used
+ *
+ * @throws IOException When touch fails
+ */
+ public function touch($files, $time = null, $atime = null)
+ {
+ foreach ($this->toIterable($files) as $file) {
+ $touch = $time ? @touch($file, $time, $atime) : @touch($file);
+ if (true !== $touch) {
+ throw new IOException(sprintf('Failed to touch "%s".', $file), 0, null, $file);
+ }
+ }
+ }
+
+ /**
+ * Removes files or directories.
+ *
+ * @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove
+ *
+ * @throws IOException When removal fails
+ */
+ public function remove($files)
+ {
+ if ($files instanceof \Traversable) {
+ $files = iterator_to_array($files, false);
+ } elseif (!\is_array($files)) {
+ $files = [$files];
+ }
+ $files = array_reverse($files);
+ foreach ($files as $file) {
+ if (is_link($file)) {
+ // See https://bugs.php.net/52176
+ if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) {
+ throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, self::$lastError));
+ }
+ } elseif (is_dir($file)) {
+ $this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS));
+
+ if (!self::box('rmdir', $file) && file_exists($file)) {
+ throw new IOException(sprintf('Failed to remove directory "%s": %s.', $file, self::$lastError));
+ }
+ } elseif (!self::box('unlink', $file) && file_exists($file)) {
+ throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, self::$lastError));
+ }
+ }
+ }
+
+ /**
+ * Change mode for an array of files or directories.
+ *
+ * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change mode
+ * @param int $mode The new mode (octal)
+ * @param int $umask The mode mask (octal)
+ * @param bool $recursive Whether change the mod recursively or not
+ *
+ * @throws IOException When the change fails
+ */
+ public function chmod($files, $mode, $umask = 0000, $recursive = false)
+ {
+ foreach ($this->toIterable($files) as $file) {
+ if (true !== @chmod($file, $mode & ~$umask)) {
+ throw new IOException(sprintf('Failed to chmod file "%s".', $file), 0, null, $file);
+ }
+ if ($recursive && is_dir($file) && !is_link($file)) {
+ $this->chmod(new \FilesystemIterator($file), $mode, $umask, true);
+ }
+ }
+ }
+
+ /**
+ * Change the owner of an array of files or directories.
+ *
+ * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change owner
+ * @param string|int $user A user name or number
+ * @param bool $recursive Whether change the owner recursively or not
+ *
+ * @throws IOException When the change fails
+ */
+ public function chown($files, $user, $recursive = false)
+ {
+ foreach ($this->toIterable($files) as $file) {
+ if ($recursive && is_dir($file) && !is_link($file)) {
+ $this->chown(new \FilesystemIterator($file), $user, true);
+ }
+ if (is_link($file) && \function_exists('lchown')) {
+ if (true !== @lchown($file, $user)) {
+ throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file);
+ }
+ } else {
+ if (true !== @chown($file, $user)) {
+ throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file);
+ }
+ }
+ }
+ }
+
+ /**
+ * Change the group of an array of files or directories.
+ *
+ * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change group
+ * @param string|int $group A group name or number
+ * @param bool $recursive Whether change the group recursively or not
+ *
+ * @throws IOException When the change fails
+ */
+ public function chgrp($files, $group, $recursive = false)
+ {
+ foreach ($this->toIterable($files) as $file) {
+ if ($recursive && is_dir($file) && !is_link($file)) {
+ $this->chgrp(new \FilesystemIterator($file), $group, true);
+ }
+ if (is_link($file) && \function_exists('lchgrp')) {
+ if (true !== @lchgrp($file, $group) || (\defined('HHVM_VERSION') && !posix_getgrnam($group))) {
+ throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file);
+ }
+ } else {
+ if (true !== @chgrp($file, $group)) {
+ throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file);
+ }
+ }
+ }
+ }
+
+ /**
+ * Renames a file or a directory.
+ *
+ * @param string $origin The origin filename or directory
+ * @param string $target The new filename or directory
+ * @param bool $overwrite Whether to overwrite the target if it already exists
+ *
+ * @throws IOException When target file or directory already exists
+ * @throws IOException When origin cannot be renamed
+ */
+ public function rename($origin, $target, $overwrite = false)
+ {
+ // we check that target does not exist
+ if (!$overwrite && $this->isReadable($target)) {
+ throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target);
+ }
+
+ if (true !== @rename($origin, $target)) {
+ if (is_dir($origin)) {
+ // See https://bugs.php.net/54097 & https://php.net/rename#113943
+ $this->mirror($origin, $target, null, ['override' => $overwrite, 'delete' => $overwrite]);
+ $this->remove($origin);
+
+ return;
+ }
+ throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target);
+ }
+ }
+
+ /**
+ * Tells whether a file exists and is readable.
+ *
+ * @param string $filename Path to the file
+ *
+ * @return bool
+ *
+ * @throws IOException When windows path is longer than 258 characters
+ */
+ private function isReadable($filename)
+ {
+ $maxPathLength = PHP_MAXPATHLEN - 2;
+
+ if (\strlen($filename) > $maxPathLength) {
+ throw new IOException(sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename);
+ }
+
+ return is_readable($filename);
+ }
+
+ /**
+ * Creates a symbolic link or copy a directory.
+ *
+ * @param string $originDir The origin directory path
+ * @param string $targetDir The symbolic link name
+ * @param bool $copyOnWindows Whether to copy files if on Windows
+ *
+ * @throws IOException When symlink fails
+ */
+ public function symlink($originDir, $targetDir, $copyOnWindows = false)
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $originDir = strtr($originDir, '/', '\\');
+ $targetDir = strtr($targetDir, '/', '\\');
+
+ if ($copyOnWindows) {
+ $this->mirror($originDir, $targetDir);
+
+ return;
+ }
+ }
+
+ $this->mkdir(\dirname($targetDir));
+
+ if (is_link($targetDir)) {
+ if (readlink($targetDir) === $originDir) {
+ return;
+ }
+ $this->remove($targetDir);
+ }
+
+ if (!self::box('symlink', $originDir, $targetDir)) {
+ $this->linkException($originDir, $targetDir, 'symbolic');
+ }
+ }
+
+ /**
+ * Creates a hard link, or several hard links to a file.
+ *
+ * @param string $originFile The original file
+ * @param string|string[] $targetFiles The target file(s)
+ *
+ * @throws FileNotFoundException When original file is missing or not a file
+ * @throws IOException When link fails, including if link already exists
+ */
+ public function hardlink($originFile, $targetFiles)
+ {
+ if (!$this->exists($originFile)) {
+ throw new FileNotFoundException(null, 0, null, $originFile);
+ }
+
+ if (!is_file($originFile)) {
+ throw new FileNotFoundException(sprintf('Origin file "%s" is not a file', $originFile));
+ }
+
+ foreach ($this->toIterable($targetFiles) as $targetFile) {
+ if (is_file($targetFile)) {
+ if (fileinode($originFile) === fileinode($targetFile)) {
+ continue;
+ }
+ $this->remove($targetFile);
+ }
+
+ if (!self::box('link', $originFile, $targetFile)) {
+ $this->linkException($originFile, $targetFile, 'hard');
+ }
+ }
+ }
+
+ /**
+ * @param string $origin
+ * @param string $target
+ * @param string $linkType Name of the link type, typically 'symbolic' or 'hard'
+ */
+ private function linkException($origin, $target, $linkType)
+ {
+ if (self::$lastError) {
+ if ('\\' === \DIRECTORY_SEPARATOR && false !== strpos(self::$lastError, 'error code(1314)')) {
+ throw new IOException(sprintf('Unable to create %s link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target);
+ }
+ }
+ throw new IOException(sprintf('Failed to create %s link from "%s" to "%s".', $linkType, $origin, $target), 0, null, $target);
+ }
+
+ /**
+ * Resolves links in paths.
+ *
+ * With $canonicalize = false (default)
+ * - if $path does not exist or is not a link, returns null
+ * - if $path is a link, returns the next direct target of the link without considering the existence of the target
+ *
+ * With $canonicalize = true
+ * - if $path does not exist, returns null
+ * - if $path exists, returns its absolute fully resolved final version
+ *
+ * @param string $path A filesystem path
+ * @param bool $canonicalize Whether or not to return a canonicalized path
+ *
+ * @return string|null
+ */
+ public function readlink($path, $canonicalize = false)
+ {
+ if (!$canonicalize && !is_link($path)) {
+ return null;
+ }
+
+ if ($canonicalize) {
+ if (!$this->exists($path)) {
+ return null;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $path = readlink($path);
+ }
+
+ return realpath($path);
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ return realpath($path);
+ }
+
+ return readlink($path);
+ }
+
+ /**
+ * Given an existing path, convert it to a path relative to a given starting path.
+ *
+ * @param string $endPath Absolute path of target
+ * @param string $startPath Absolute path where traversal begins
+ *
+ * @return string Path of target relative to starting path
+ */
+ public function makePathRelative($endPath, $startPath)
+ {
+ if (!$this->isAbsolutePath($endPath) || !$this->isAbsolutePath($startPath)) {
+ @trigger_error(sprintf('Support for passing relative paths to %s() is deprecated since Symfony 3.4 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
+ }
+
+ // Normalize separators on Windows
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $endPath = str_replace('\\', '/', $endPath);
+ $startPath = str_replace('\\', '/', $startPath);
+ }
+
+ $stripDriveLetter = function ($path) {
+ if (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) {
+ return substr($path, 2);
+ }
+
+ return $path;
+ };
+
+ $endPath = $stripDriveLetter($endPath);
+ $startPath = $stripDriveLetter($startPath);
+
+ // Split the paths into arrays
+ $startPathArr = explode('/', trim($startPath, '/'));
+ $endPathArr = explode('/', trim($endPath, '/'));
+
+ $normalizePathArray = function ($pathSegments, $absolute) {
+ $result = [];
+
+ foreach ($pathSegments as $segment) {
+ if ('..' === $segment && ($absolute || \count($result))) {
+ array_pop($result);
+ } elseif ('.' !== $segment) {
+ $result[] = $segment;
+ }
+ }
+
+ return $result;
+ };
+
+ $startPathArr = $normalizePathArray($startPathArr, static::isAbsolutePath($startPath));
+ $endPathArr = $normalizePathArray($endPathArr, static::isAbsolutePath($endPath));
+
+ // Find for which directory the common path stops
+ $index = 0;
+ while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) {
+ ++$index;
+ }
+
+ // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
+ if (1 === \count($startPathArr) && '' === $startPathArr[0]) {
+ $depth = 0;
+ } else {
+ $depth = \count($startPathArr) - $index;
+ }
+
+ // Repeated "../" for each level need to reach the common path
+ $traverser = str_repeat('../', $depth);
+
+ $endPathRemainder = implode('/', \array_slice($endPathArr, $index));
+
+ // Construct $endPath from traversing to the common path, then to the remaining $endPath
+ $relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : '');
+
+ return '' === $relativePath ? './' : $relativePath;
+ }
+
+ /**
+ * Mirrors a directory to another.
+ *
+ * Copies files and directories from the origin directory into the target directory. By default:
+ *
+ * - existing files in the target directory will be overwritten, except if they are newer (see the `override` option)
+ * - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option)
+ *
+ * @param string $originDir The origin directory
+ * @param string $targetDir The target directory
+ * @param \Traversable|null $iterator Iterator that filters which files and directories to copy, if null a recursive iterator is created
+ * @param array $options An array of boolean options
+ * Valid options are:
+ * - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false)
+ * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false)
+ * - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
+ *
+ * @throws IOException When file type is unknown
+ */
+ public function mirror($originDir, $targetDir, \Traversable $iterator = null, $options = [])
+ {
+ $targetDir = rtrim($targetDir, '/\\');
+ $originDir = rtrim($originDir, '/\\');
+ $originDirLen = \strlen($originDir);
+
+ // Iterate in destination folder to remove obsolete entries
+ if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) {
+ $deleteIterator = $iterator;
+ if (null === $deleteIterator) {
+ $flags = \FilesystemIterator::SKIP_DOTS;
+ $deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST);
+ }
+ $targetDirLen = \strlen($targetDir);
+ foreach ($deleteIterator as $file) {
+ $origin = $originDir.substr($file->getPathname(), $targetDirLen);
+ if (!$this->exists($origin)) {
+ $this->remove($file);
+ }
+ }
+ }
+
+ $copyOnWindows = false;
+ if (isset($options['copy_on_windows'])) {
+ $copyOnWindows = $options['copy_on_windows'];
+ }
+
+ if (null === $iterator) {
+ $flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS;
+ $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST);
+ }
+
+ if ($this->exists($originDir)) {
+ $this->mkdir($targetDir);
+ }
+
+ foreach ($iterator as $file) {
+ $target = $targetDir.substr($file->getPathname(), $originDirLen);
+
+ if ($copyOnWindows) {
+ if (is_file($file)) {
+ $this->copy($file, $target, isset($options['override']) ? $options['override'] : false);
+ } elseif (is_dir($file)) {
+ $this->mkdir($target);
+ } else {
+ throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file);
+ }
+ } else {
+ if (is_link($file)) {
+ $this->symlink($file->getLinkTarget(), $target);
+ } elseif (is_dir($file)) {
+ $this->mkdir($target);
+ } elseif (is_file($file)) {
+ $this->copy($file, $target, isset($options['override']) ? $options['override'] : false);
+ } else {
+ throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns whether the file path is an absolute path.
+ *
+ * @param string $file A file path
+ *
+ * @return bool
+ */
+ public function isAbsolutePath($file)
+ {
+ return strspn($file, '/\\', 0, 1)
+ || (\strlen($file) > 3 && ctype_alpha($file[0])
+ && ':' === $file[1]
+ && strspn($file, '/\\', 2, 1)
+ )
+ || null !== parse_url($file, PHP_URL_SCHEME)
+ ;
+ }
+
+ /**
+ * Creates a temporary file with support for custom stream wrappers.
+ *
+ * @param string $dir The directory where the temporary filename will be created
+ * @param string $prefix The prefix of the generated temporary filename
+ * Note: Windows uses only the first three characters of prefix
+ *
+ * @return string The new temporary filename (with path), or throw an exception on failure
+ */
+ public function tempnam($dir, $prefix)
+ {
+ list($scheme, $hierarchy) = $this->getSchemeAndHierarchy($dir);
+
+ // If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem
+ if (null === $scheme || 'file' === $scheme || 'gs' === $scheme) {
+ $tmpFile = @tempnam($hierarchy, $prefix);
+
+ // If tempnam failed or no scheme return the filename otherwise prepend the scheme
+ if (false !== $tmpFile) {
+ if (null !== $scheme && 'gs' !== $scheme) {
+ return $scheme.'://'.$tmpFile;
+ }
+
+ return $tmpFile;
+ }
+
+ throw new IOException('A temporary file could not be created.');
+ }
+
+ // Loop until we create a valid temp file or have reached 10 attempts
+ for ($i = 0; $i < 10; ++$i) {
+ // Create a unique filename
+ $tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true);
+
+ // Use fopen instead of file_exists as some streams do not support stat
+ // Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability
+ $handle = @fopen($tmpFile, 'x+');
+
+ // If unsuccessful restart the loop
+ if (false === $handle) {
+ continue;
+ }
+
+ // Close the file if it was successfully opened
+ @fclose($handle);
+
+ return $tmpFile;
+ }
+
+ throw new IOException('A temporary file could not be created.');
+ }
+
+ /**
+ * Atomically dumps content into a file.
+ *
+ * @param string $filename The file to be written to
+ * @param string $content The data to write into the file
+ *
+ * @throws IOException if the file cannot be written to
+ */
+ public function dumpFile($filename, $content)
+ {
+ $dir = \dirname($filename);
+
+ if (!is_dir($dir)) {
+ $this->mkdir($dir);
+ }
+
+ if (!is_writable($dir)) {
+ throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
+ }
+
+ // Will create a temp file with 0600 access rights
+ // when the filesystem supports chmod.
+ $tmpFile = $this->tempnam($dir, basename($filename));
+
+ if (false === @file_put_contents($tmpFile, $content)) {
+ throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
+ }
+
+ @chmod($tmpFile, file_exists($filename) ? fileperms($filename) : 0666 & ~umask());
+
+ $this->rename($tmpFile, $filename, true);
+ }
+
+ /**
+ * Appends content to an existing file.
+ *
+ * @param string $filename The file to which to append content
+ * @param string $content The content to append
+ *
+ * @throws IOException If the file is not writable
+ */
+ public function appendToFile($filename, $content)
+ {
+ $dir = \dirname($filename);
+
+ if (!is_dir($dir)) {
+ $this->mkdir($dir);
+ }
+
+ if (!is_writable($dir)) {
+ throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
+ }
+
+ if (false === @file_put_contents($filename, $content, FILE_APPEND)) {
+ throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
+ }
+ }
+
+ /**
+ * @param mixed $files
+ *
+ * @return array|\Traversable
+ */
+ private function toIterable($files)
+ {
+ return \is_array($files) || $files instanceof \Traversable ? $files : [$files];
+ }
+
+ /**
+ * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> [file, tmp]).
+ *
+ * @param string $filename The filename to be parsed
+ *
+ * @return array The filename scheme and hierarchical part
+ */
+ private function getSchemeAndHierarchy($filename)
+ {
+ $components = explode('://', $filename, 2);
+
+ return 2 === \count($components) ? [$components[0], $components[1]] : [null, $components[0]];
+ }
+
+ /**
+ * @param callable $func
+ *
+ * @return mixed
+ */
+ private static function box($func)
+ {
+ self::$lastError = null;
+ set_error_handler(__CLASS__.'::handleError');
+ try {
+ $result = \call_user_func_array($func, \array_slice(\func_get_args(), 1));
+ restore_error_handler();
+
+ return $result;
+ } catch (\Throwable $e) {
+ } catch (\Exception $e) {
+ }
+ restore_error_handler();
+
+ throw $e;
+ }
+
+ /**
+ * @internal
+ */
+ public static function handleError($type, $msg)
+ {
+ self::$lastError = $msg;
+ }
+}
diff --git a/console/skel/symfony/filesystem/LICENSE b/console/skel/symfony/filesystem/LICENSE
new file mode 100644
index 0000000..9e936ec
--- /dev/null
+++ b/console/skel/symfony/filesystem/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2020 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/console/skel/symfony/filesystem/LockHandler.php b/console/skel/symfony/filesystem/LockHandler.php
new file mode 100644
index 0000000..8e0eb74
--- /dev/null
+++ b/console/skel/symfony/filesystem/LockHandler.php
@@ -0,0 +1,121 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Filesystem;
+
+use Symfony\Component\Filesystem\Exception\IOException;
+use Symfony\Component\Lock\Store\FlockStore;
+use Symfony\Component\Lock\Store\SemaphoreStore;
+
+@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use %s or %s instead.', LockHandler::class, SemaphoreStore::class, FlockStore::class), E_USER_DEPRECATED);
+
+/**
+ * LockHandler class provides a simple abstraction to lock anything by means of
+ * a file lock.
+ *
+ * A locked file is created based on the lock name when calling lock(). Other
+ * lock handlers will not be able to lock the same name until it is released
+ * (explicitly by calling release() or implicitly when the instance holding the
+ * lock is destroyed).
+ *
+ * @author Grégoire Pineau
+ * @author Romain Neutron
+ * @author Nicolas Grekas
+ *
+ * @deprecated since version 3.4, to be removed in 4.0. Use Symfony\Component\Lock\Store\SemaphoreStore or Symfony\Component\Lock\Store\FlockStore instead.
+ */
+class LockHandler
+{
+ private $file;
+ private $handle;
+
+ /**
+ * @param string $name The lock name
+ * @param string|null $lockPath The directory to store the lock. Default values will use temporary directory
+ *
+ * @throws IOException If the lock directory could not be created or is not writable
+ */
+ public function __construct($name, $lockPath = null)
+ {
+ $lockPath = $lockPath ?: sys_get_temp_dir();
+
+ if (!is_dir($lockPath)) {
+ $fs = new Filesystem();
+ $fs->mkdir($lockPath);
+ }
+
+ if (!is_writable($lockPath)) {
+ throw new IOException(sprintf('The directory "%s" is not writable.', $lockPath), 0, null, $lockPath);
+ }
+
+ $this->file = sprintf('%s/sf.%s.%s.lock', $lockPath, preg_replace('/[^a-z0-9\._-]+/i', '-', $name), hash('sha256', $name));
+ }
+
+ /**
+ * Lock the resource.
+ *
+ * @param bool $blocking Wait until the lock is released
+ *
+ * @return bool Returns true if the lock was acquired, false otherwise
+ *
+ * @throws IOException If the lock file could not be created or opened
+ */
+ public function lock($blocking = false)
+ {
+ if ($this->handle) {
+ return true;
+ }
+
+ $error = null;
+
+ // Silence error reporting
+ set_error_handler(function ($errno, $msg) use (&$error) {
+ $error = $msg;
+ });
+
+ if (!$this->handle = fopen($this->file, 'r+') ?: fopen($this->file, 'r')) {
+ if ($this->handle = fopen($this->file, 'x')) {
+ chmod($this->file, 0666);
+ } elseif (!$this->handle = fopen($this->file, 'r+') ?: fopen($this->file, 'r')) {
+ usleep(100); // Give some time for chmod() to complete
+ $this->handle = fopen($this->file, 'r+') ?: fopen($this->file, 'r');
+ }
+ }
+ restore_error_handler();
+
+ if (!$this->handle) {
+ throw new IOException($error, 0, null, $this->file);
+ }
+
+ // On Windows, even if PHP doc says the contrary, LOCK_NB works, see
+ // https://bugs.php.net/54129
+ if (!flock($this->handle, LOCK_EX | ($blocking ? 0 : LOCK_NB))) {
+ fclose($this->handle);
+ $this->handle = null;
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Release the resource.
+ */
+ public function release()
+ {
+ if ($this->handle) {
+ flock($this->handle, LOCK_UN | LOCK_NB);
+ fclose($this->handle);
+ $this->handle = null;
+ }
+ }
+}
diff --git a/console/skel/symfony/filesystem/README.md b/console/skel/symfony/filesystem/README.md
new file mode 100644
index 0000000..cb03d43
--- /dev/null
+++ b/console/skel/symfony/filesystem/README.md
@@ -0,0 +1,13 @@
+Filesystem Component
+====================
+
+The Filesystem component provides basic utilities for the filesystem.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/filesystem.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/console/skel/symfony/filesystem/Tests/ExceptionTest.php b/console/skel/symfony/filesystem/Tests/ExceptionTest.php
new file mode 100644
index 0000000..300acf1
--- /dev/null
+++ b/console/skel/symfony/filesystem/Tests/ExceptionTest.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Filesystem\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Filesystem\Exception\FileNotFoundException;
+use Symfony\Component\Filesystem\Exception\IOException;
+
+/**
+ * Test class for Filesystem.
+ */
+class ExceptionTest extends TestCase
+{
+ public function testGetPath()
+ {
+ $e = new IOException('', 0, null, '/foo');
+ $this->assertEquals('/foo', $e->getPath(), 'The pass should be returned.');
+ }
+
+ public function testGeneratedMessage()
+ {
+ $e = new FileNotFoundException(null, 0, null, '/foo');
+ $this->assertEquals('/foo', $e->getPath());
+ $this->assertEquals('File "/foo" could not be found.', $e->getMessage(), 'A message should be generated.');
+ }
+
+ public function testGeneratedMessageWithoutPath()
+ {
+ $e = new FileNotFoundException();
+ $this->assertEquals('File could not be found.', $e->getMessage(), 'A message should be generated.');
+ }
+
+ public function testCustomMessage()
+ {
+ $e = new FileNotFoundException('bar', 0, null, '/foo');
+ $this->assertEquals('bar', $e->getMessage(), 'A custom message should be possible still.');
+ }
+}
diff --git a/console/skel/symfony/filesystem/Tests/FilesystemTest.php b/console/skel/symfony/filesystem/Tests/FilesystemTest.php
new file mode 100644
index 0000000..e9e7784
--- /dev/null
+++ b/console/skel/symfony/filesystem/Tests/FilesystemTest.php
@@ -0,0 +1,1656 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Filesystem\Tests;
+
+/**
+ * Test class for Filesystem.
+ */
+class FilesystemTest extends FilesystemTestCase
+{
+ public function testCopyCreatesNewFile()
+ {
+ $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
+ $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
+
+ file_put_contents($sourceFilePath, 'SOURCE FILE');
+
+ $this->filesystem->copy($sourceFilePath, $targetFilePath);
+
+ $this->assertFileExists($targetFilePath);
+ $this->assertStringEqualsFile($targetFilePath, 'SOURCE FILE');
+ }
+
+ public function testCopyFails()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
+ $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
+
+ $this->filesystem->copy($sourceFilePath, $targetFilePath);
+ }
+
+ public function testCopyUnreadableFileFails()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ // skip test on Windows; PHP can't easily set file as unreadable on Windows
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('This test cannot run on Windows.');
+ }
+
+ if (!getenv('USER') || 'root' === getenv('USER')) {
+ $this->markTestSkipped('This test will fail if run under superuser');
+ }
+
+ $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
+ $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
+
+ file_put_contents($sourceFilePath, 'SOURCE FILE');
+
+ // make sure target cannot be read
+ $this->filesystem->chmod($sourceFilePath, 0222);
+
+ $this->filesystem->copy($sourceFilePath, $targetFilePath);
+ }
+
+ public function testCopyOverridesExistingFileIfModified()
+ {
+ $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
+ $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
+
+ file_put_contents($sourceFilePath, 'SOURCE FILE');
+ file_put_contents($targetFilePath, 'TARGET FILE');
+ touch($targetFilePath, time() - 1000);
+
+ $this->filesystem->copy($sourceFilePath, $targetFilePath);
+
+ $this->assertFileExists($targetFilePath);
+ $this->assertStringEqualsFile($targetFilePath, 'SOURCE FILE');
+ }
+
+ public function testCopyDoesNotOverrideExistingFileByDefault()
+ {
+ $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
+ $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
+
+ file_put_contents($sourceFilePath, 'SOURCE FILE');
+ file_put_contents($targetFilePath, 'TARGET FILE');
+
+ // make sure both files have the same modification time
+ $modificationTime = time() - 1000;
+ touch($sourceFilePath, $modificationTime);
+ touch($targetFilePath, $modificationTime);
+
+ $this->filesystem->copy($sourceFilePath, $targetFilePath);
+
+ $this->assertFileExists($targetFilePath);
+ $this->assertStringEqualsFile($targetFilePath, 'TARGET FILE');
+ }
+
+ public function testCopyOverridesExistingFileIfForced()
+ {
+ $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
+ $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
+
+ file_put_contents($sourceFilePath, 'SOURCE FILE');
+ file_put_contents($targetFilePath, 'TARGET FILE');
+
+ // make sure both files have the same modification time
+ $modificationTime = time() - 1000;
+ touch($sourceFilePath, $modificationTime);
+ touch($targetFilePath, $modificationTime);
+
+ $this->filesystem->copy($sourceFilePath, $targetFilePath, true);
+
+ $this->assertFileExists($targetFilePath);
+ $this->assertStringEqualsFile($targetFilePath, 'SOURCE FILE');
+ }
+
+ public function testCopyWithOverrideWithReadOnlyTargetFails()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ // skip test on Windows; PHP can't easily set file as unwritable on Windows
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('This test cannot run on Windows.');
+ }
+
+ if (!getenv('USER') || 'root' === getenv('USER')) {
+ $this->markTestSkipped('This test will fail if run under superuser');
+ }
+
+ $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
+ $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
+
+ file_put_contents($sourceFilePath, 'SOURCE FILE');
+ file_put_contents($targetFilePath, 'TARGET FILE');
+
+ // make sure both files have the same modification time
+ $modificationTime = time() - 1000;
+ touch($sourceFilePath, $modificationTime);
+ touch($targetFilePath, $modificationTime);
+
+ // make sure target is read-only
+ $this->filesystem->chmod($targetFilePath, 0444);
+
+ $this->filesystem->copy($sourceFilePath, $targetFilePath, true);
+ }
+
+ public function testCopyCreatesTargetDirectoryIfItDoesNotExist()
+ {
+ $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
+ $targetFileDirectory = $this->workspace.\DIRECTORY_SEPARATOR.'directory';
+ $targetFilePath = $targetFileDirectory.\DIRECTORY_SEPARATOR.'copy_target_file';
+
+ file_put_contents($sourceFilePath, 'SOURCE FILE');
+
+ $this->filesystem->copy($sourceFilePath, $targetFilePath);
+
+ $this->assertDirectoryExists($targetFileDirectory);
+ $this->assertFileExists($targetFilePath);
+ $this->assertStringEqualsFile($targetFilePath, 'SOURCE FILE');
+ }
+
+ /**
+ * @group network
+ */
+ public function testCopyForOriginUrlsAndExistingLocalFileDefaultsToCopy()
+ {
+ if (!\in_array('https', stream_get_wrappers())) {
+ $this->markTestSkipped('"https" stream wrapper is not enabled.');
+ }
+ $sourceFilePath = 'https://symfony.com/images/common/logo/logo_symfony_header.png';
+ $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
+
+ file_put_contents($targetFilePath, 'TARGET FILE');
+
+ $this->filesystem->copy($sourceFilePath, $targetFilePath, false);
+
+ $this->assertFileExists($targetFilePath);
+ $this->assertEquals(file_get_contents($sourceFilePath), file_get_contents($targetFilePath));
+ }
+
+ public function testMkdirCreatesDirectoriesRecursively()
+ {
+ $directory = $this->workspace
+ .\DIRECTORY_SEPARATOR.'directory'
+ .\DIRECTORY_SEPARATOR.'sub_directory';
+
+ $this->filesystem->mkdir($directory);
+
+ $this->assertDirectoryExists($directory);
+ }
+
+ public function testMkdirCreatesDirectoriesFromArray()
+ {
+ $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
+ $directories = [
+ $basePath.'1', $basePath.'2', $basePath.'3',
+ ];
+
+ $this->filesystem->mkdir($directories);
+
+ $this->assertDirectoryExists($basePath.'1');
+ $this->assertDirectoryExists($basePath.'2');
+ $this->assertDirectoryExists($basePath.'3');
+ }
+
+ public function testMkdirCreatesDirectoriesFromTraversableObject()
+ {
+ $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
+ $directories = new \ArrayObject([
+ $basePath.'1', $basePath.'2', $basePath.'3',
+ ]);
+
+ $this->filesystem->mkdir($directories);
+
+ $this->assertDirectoryExists($basePath.'1');
+ $this->assertDirectoryExists($basePath.'2');
+ $this->assertDirectoryExists($basePath.'3');
+ }
+
+ public function testMkdirCreatesDirectoriesFails()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
+ $dir = $basePath.'2';
+
+ file_put_contents($dir, '');
+
+ $this->filesystem->mkdir($dir);
+ }
+
+ public function testTouchCreatesEmptyFile()
+ {
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'1';
+
+ $this->filesystem->touch($file);
+
+ $this->assertFileExists($file);
+ }
+
+ public function testTouchFails()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'1'.\DIRECTORY_SEPARATOR.'2';
+
+ $this->filesystem->touch($file);
+ }
+
+ public function testTouchCreatesEmptyFilesFromArray()
+ {
+ $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
+ $files = [
+ $basePath.'1', $basePath.'2', $basePath.'3',
+ ];
+
+ $this->filesystem->touch($files);
+
+ $this->assertFileExists($basePath.'1');
+ $this->assertFileExists($basePath.'2');
+ $this->assertFileExists($basePath.'3');
+ }
+
+ public function testTouchCreatesEmptyFilesFromTraversableObject()
+ {
+ $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
+ $files = new \ArrayObject([
+ $basePath.'1', $basePath.'2', $basePath.'3',
+ ]);
+
+ $this->filesystem->touch($files);
+
+ $this->assertFileExists($basePath.'1');
+ $this->assertFileExists($basePath.'2');
+ $this->assertFileExists($basePath.'3');
+ }
+
+ public function testRemoveCleansFilesAndDirectoriesIteratively()
+ {
+ $basePath = $this->workspace.\DIRECTORY_SEPARATOR.'directory'.\DIRECTORY_SEPARATOR;
+
+ mkdir($basePath);
+ mkdir($basePath.'dir');
+ touch($basePath.'file');
+
+ $this->filesystem->remove($basePath);
+
+ $this->assertFileNotExists($basePath);
+ }
+
+ public function testRemoveCleansArrayOfFilesAndDirectories()
+ {
+ $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
+
+ mkdir($basePath.'dir');
+ touch($basePath.'file');
+
+ $files = [
+ $basePath.'dir', $basePath.'file',
+ ];
+
+ $this->filesystem->remove($files);
+
+ $this->assertFileNotExists($basePath.'dir');
+ $this->assertFileNotExists($basePath.'file');
+ }
+
+ public function testRemoveCleansTraversableObjectOfFilesAndDirectories()
+ {
+ $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
+
+ mkdir($basePath.'dir');
+ touch($basePath.'file');
+
+ $files = new \ArrayObject([
+ $basePath.'dir', $basePath.'file',
+ ]);
+
+ $this->filesystem->remove($files);
+
+ $this->assertFileNotExists($basePath.'dir');
+ $this->assertFileNotExists($basePath.'file');
+ }
+
+ public function testRemoveIgnoresNonExistingFiles()
+ {
+ $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
+
+ mkdir($basePath.'dir');
+
+ $files = [
+ $basePath.'dir', $basePath.'file',
+ ];
+
+ $this->filesystem->remove($files);
+
+ $this->assertFileNotExists($basePath.'dir');
+ }
+
+ public function testRemoveCleansInvalidLinks()
+ {
+ $this->markAsSkippedIfSymlinkIsMissing();
+
+ $basePath = $this->workspace.\DIRECTORY_SEPARATOR.'directory'.\DIRECTORY_SEPARATOR;
+
+ mkdir($basePath);
+ mkdir($basePath.'dir');
+ // create symlink to nonexistent file
+ @symlink($basePath.'file', $basePath.'file-link');
+
+ // create symlink to dir using trailing forward slash
+ $this->filesystem->symlink($basePath.'dir/', $basePath.'dir-link');
+ $this->assertDirectoryExists($basePath.'dir-link');
+
+ // create symlink to nonexistent dir
+ rmdir($basePath.'dir');
+ $this->assertFalse('\\' === \DIRECTORY_SEPARATOR ? @readlink($basePath.'dir-link') : is_dir($basePath.'dir-link'));
+
+ $this->filesystem->remove($basePath);
+
+ $this->assertFileNotExists($basePath);
+ }
+
+ public function testFilesExists()
+ {
+ $basePath = $this->workspace.\DIRECTORY_SEPARATOR.'directory'.\DIRECTORY_SEPARATOR;
+
+ mkdir($basePath);
+ touch($basePath.'file1');
+ mkdir($basePath.'folder');
+
+ $this->assertTrue($this->filesystem->exists($basePath.'file1'));
+ $this->assertTrue($this->filesystem->exists($basePath.'folder'));
+ }
+
+ public function testFilesExistsFails()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Long file names are an issue on Windows');
+ }
+ $basePath = $this->workspace.'\\directory\\';
+ $maxPathLength = PHP_MAXPATHLEN - 2;
+
+ $oldPath = getcwd();
+ mkdir($basePath);
+ chdir($basePath);
+ $file = str_repeat('T', $maxPathLength - \strlen($basePath) + 1);
+ $path = $basePath.$file;
+ exec('TYPE NUL >>'.$file); // equivalent of touch, we can not use the php touch() here because it suffers from the same limitation
+ $this->longPathNamesWindows[] = $path; // save this so we can clean up later
+ chdir($oldPath);
+ $this->filesystem->exists($path);
+ }
+
+ public function testFilesExistsTraversableObjectOfFilesAndDirectories()
+ {
+ $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
+
+ mkdir($basePath.'dir');
+ touch($basePath.'file');
+
+ $files = new \ArrayObject([
+ $basePath.'dir', $basePath.'file',
+ ]);
+
+ $this->assertTrue($this->filesystem->exists($files));
+ }
+
+ public function testFilesNotExistsTraversableObjectOfFilesAndDirectories()
+ {
+ $basePath = $this->workspace.\DIRECTORY_SEPARATOR;
+
+ mkdir($basePath.'dir');
+ touch($basePath.'file');
+ touch($basePath.'file2');
+
+ $files = new \ArrayObject([
+ $basePath.'dir', $basePath.'file', $basePath.'file2',
+ ]);
+
+ unlink($basePath.'file');
+
+ $this->assertFalse($this->filesystem->exists($files));
+ }
+
+ public function testInvalidFileNotExists()
+ {
+ $basePath = $this->workspace.\DIRECTORY_SEPARATOR.'directory'.\DIRECTORY_SEPARATOR;
+
+ $this->assertFalse($this->filesystem->exists($basePath.time()));
+ }
+
+ public function testChmodChangesFileMode()
+ {
+ $this->markAsSkippedIfChmodIsMissing();
+
+ $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
+ mkdir($dir);
+ $file = $dir.\DIRECTORY_SEPARATOR.'file';
+ touch($file);
+
+ $this->filesystem->chmod($file, 0400);
+ $this->filesystem->chmod($dir, 0753);
+
+ $this->assertFilePermissions(753, $dir);
+ $this->assertFilePermissions(400, $file);
+ }
+
+ public function testChmodWithWrongModLeavesPreviousPermissionsUntouched()
+ {
+ $this->markAsSkippedIfChmodIsMissing();
+
+ if (\defined('HHVM_VERSION')) {
+ $this->markTestSkipped('chmod() changes permissions even when passing invalid modes on HHVM');
+ }
+
+ $dir = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ touch($dir);
+
+ $permissions = fileperms($dir);
+
+ $this->filesystem->chmod($dir, 'Wrongmode');
+
+ $this->assertSame($permissions, fileperms($dir));
+ }
+
+ public function testChmodRecursive()
+ {
+ $this->markAsSkippedIfChmodIsMissing();
+
+ $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
+ mkdir($dir);
+ $file = $dir.\DIRECTORY_SEPARATOR.'file';
+ touch($file);
+
+ $this->filesystem->chmod($file, 0400, 0000, true);
+ $this->filesystem->chmod($dir, 0753, 0000, true);
+
+ $this->assertFilePermissions(753, $dir);
+ $this->assertFilePermissions(753, $file);
+ }
+
+ public function testChmodAppliesUmask()
+ {
+ $this->markAsSkippedIfChmodIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ touch($file);
+
+ $this->filesystem->chmod($file, 0770, 0022);
+ $this->assertFilePermissions(750, $file);
+ }
+
+ public function testChmodChangesModeOfArrayOfFiles()
+ {
+ $this->markAsSkippedIfChmodIsMissing();
+
+ $directory = $this->workspace.\DIRECTORY_SEPARATOR.'directory';
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $files = [$directory, $file];
+
+ mkdir($directory);
+ touch($file);
+
+ $this->filesystem->chmod($files, 0753);
+
+ $this->assertFilePermissions(753, $file);
+ $this->assertFilePermissions(753, $directory);
+ }
+
+ public function testChmodChangesModeOfTraversableFileObject()
+ {
+ $this->markAsSkippedIfChmodIsMissing();
+
+ $directory = $this->workspace.\DIRECTORY_SEPARATOR.'directory';
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $files = new \ArrayObject([$directory, $file]);
+
+ mkdir($directory);
+ touch($file);
+
+ $this->filesystem->chmod($files, 0753);
+
+ $this->assertFilePermissions(753, $file);
+ $this->assertFilePermissions(753, $directory);
+ }
+
+ public function testChmodChangesZeroModeOnSubdirectoriesOnRecursive()
+ {
+ $this->markAsSkippedIfChmodIsMissing();
+
+ $directory = $this->workspace.\DIRECTORY_SEPARATOR.'directory';
+ $subdirectory = $directory.\DIRECTORY_SEPARATOR.'subdirectory';
+
+ mkdir($directory);
+ mkdir($subdirectory);
+ chmod($subdirectory, 0000);
+
+ $this->filesystem->chmod($directory, 0753, 0000, true);
+
+ $this->assertFilePermissions(753, $subdirectory);
+ }
+
+ public function testChown()
+ {
+ $this->markAsSkippedIfPosixIsMissing();
+
+ $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
+ mkdir($dir);
+
+ $owner = $this->getFileOwner($dir);
+ $this->filesystem->chown($dir, $owner);
+
+ $this->assertSame($owner, $this->getFileOwner($dir));
+ }
+
+ public function testChownRecursive()
+ {
+ $this->markAsSkippedIfPosixIsMissing();
+
+ $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
+ mkdir($dir);
+ $file = $dir.\DIRECTORY_SEPARATOR.'file';
+ touch($file);
+
+ $owner = $this->getFileOwner($dir);
+ $this->filesystem->chown($dir, $owner, true);
+
+ $this->assertSame($owner, $this->getFileOwner($file));
+ }
+
+ public function testChownSymlink()
+ {
+ $this->markAsSkippedIfSymlinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ touch($file);
+
+ $this->filesystem->symlink($file, $link);
+
+ $owner = $this->getFileOwner($link);
+ $this->filesystem->chown($link, $owner);
+
+ $this->assertSame($owner, $this->getFileOwner($link));
+ }
+
+ public function testChownLink()
+ {
+ $this->markAsSkippedIfLinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ touch($file);
+
+ $this->filesystem->hardlink($file, $link);
+
+ $owner = $this->getFileOwner($link);
+ $this->filesystem->chown($link, $owner);
+
+ $this->assertSame($owner, $this->getFileOwner($link));
+ }
+
+ public function testChownSymlinkFails()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ $this->markAsSkippedIfSymlinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ touch($file);
+
+ $this->filesystem->symlink($file, $link);
+
+ $this->filesystem->chown($link, 'user'.time().mt_rand(1000, 9999));
+ }
+
+ public function testChownLinkFails()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ $this->markAsSkippedIfLinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ touch($file);
+
+ $this->filesystem->hardlink($file, $link);
+
+ $this->filesystem->chown($link, 'user'.time().mt_rand(1000, 9999));
+ }
+
+ public function testChownFail()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ $this->markAsSkippedIfPosixIsMissing();
+
+ $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
+ mkdir($dir);
+
+ $this->filesystem->chown($dir, 'user'.time().mt_rand(1000, 9999));
+ }
+
+ public function testChgrp()
+ {
+ $this->markAsSkippedIfPosixIsMissing();
+
+ $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
+ mkdir($dir);
+
+ $group = $this->getFileGroup($dir);
+ $this->filesystem->chgrp($dir, $group);
+
+ $this->assertSame($group, $this->getFileGroup($dir));
+ }
+
+ public function testChgrpRecursive()
+ {
+ $this->markAsSkippedIfPosixIsMissing();
+
+ $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
+ mkdir($dir);
+ $file = $dir.\DIRECTORY_SEPARATOR.'file';
+ touch($file);
+
+ $group = $this->getFileGroup($dir);
+ $this->filesystem->chgrp($dir, $group, true);
+
+ $this->assertSame($group, $this->getFileGroup($file));
+ }
+
+ public function testChgrpSymlink()
+ {
+ $this->markAsSkippedIfSymlinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ touch($file);
+
+ $this->filesystem->symlink($file, $link);
+
+ $group = $this->getFileGroup($link);
+ $this->filesystem->chgrp($link, $group);
+
+ $this->assertSame($group, $this->getFileGroup($link));
+ }
+
+ public function testChgrpLink()
+ {
+ $this->markAsSkippedIfLinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ touch($file);
+
+ $this->filesystem->hardlink($file, $link);
+
+ $group = $this->getFileGroup($link);
+ $this->filesystem->chgrp($link, $group);
+
+ $this->assertSame($group, $this->getFileGroup($link));
+ }
+
+ public function testChgrpSymlinkFails()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ $this->markAsSkippedIfSymlinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ touch($file);
+
+ $this->filesystem->symlink($file, $link);
+
+ $this->filesystem->chgrp($link, 'user'.time().mt_rand(1000, 9999));
+ }
+
+ public function testChgrpLinkFails()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ $this->markAsSkippedIfLinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ touch($file);
+
+ $this->filesystem->hardlink($file, $link);
+
+ $this->filesystem->chgrp($link, 'user'.time().mt_rand(1000, 9999));
+ }
+
+ public function testChgrpFail()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ $this->markAsSkippedIfPosixIsMissing();
+
+ $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir';
+ mkdir($dir);
+
+ $this->filesystem->chgrp($dir, 'user'.time().mt_rand(1000, 9999));
+ }
+
+ public function testRename()
+ {
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $newPath = $this->workspace.\DIRECTORY_SEPARATOR.'new_file';
+ touch($file);
+
+ $this->filesystem->rename($file, $newPath);
+
+ $this->assertFileNotExists($file);
+ $this->assertFileExists($newPath);
+ }
+
+ public function testRenameThrowsExceptionIfTargetAlreadyExists()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $newPath = $this->workspace.\DIRECTORY_SEPARATOR.'new_file';
+
+ touch($file);
+ touch($newPath);
+
+ $this->filesystem->rename($file, $newPath);
+ }
+
+ public function testRenameOverwritesTheTargetIfItAlreadyExists()
+ {
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $newPath = $this->workspace.\DIRECTORY_SEPARATOR.'new_file';
+
+ touch($file);
+ touch($newPath);
+
+ $this->filesystem->rename($file, $newPath, true);
+
+ $this->assertFileNotExists($file);
+ $this->assertFileExists($newPath);
+ }
+
+ public function testRenameThrowsExceptionOnError()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.uniqid('fs_test_', true);
+ $newPath = $this->workspace.\DIRECTORY_SEPARATOR.'new_file';
+
+ $this->filesystem->rename($file, $newPath);
+ }
+
+ public function testSymlink()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support creating "broken" symlinks');
+ }
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ // $file does not exist right now: creating "broken" links is a wanted feature
+ $this->filesystem->symlink($file, $link);
+
+ $this->assertTrue(is_link($link));
+
+ // Create the linked file AFTER creating the link
+ touch($file);
+
+ $this->assertEquals($file, readlink($link));
+ }
+
+ /**
+ * @depends testSymlink
+ */
+ public function testRemoveSymlink()
+ {
+ $this->markAsSkippedIfSymlinkIsMissing();
+
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ $this->filesystem->remove($link);
+
+ $this->assertFalse(is_link($link));
+ $this->assertFalse(is_file($link));
+ $this->assertDirectoryNotExists($link);
+ }
+
+ public function testSymlinkIsOverwrittenIfPointsToDifferentTarget()
+ {
+ $this->markAsSkippedIfSymlinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ touch($file);
+ symlink($this->workspace, $link);
+
+ $this->filesystem->symlink($file, $link);
+
+ $this->assertTrue(is_link($link));
+ $this->assertEquals($file, readlink($link));
+ }
+
+ public function testSymlinkIsNotOverwrittenIfAlreadyCreated()
+ {
+ $this->markAsSkippedIfSymlinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ touch($file);
+ symlink($file, $link);
+
+ $this->filesystem->symlink($file, $link);
+
+ $this->assertTrue(is_link($link));
+ $this->assertEquals($file, readlink($link));
+ }
+
+ public function testSymlinkCreatesTargetDirectoryIfItDoesNotExist()
+ {
+ $this->markAsSkippedIfSymlinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link1 = $this->workspace.\DIRECTORY_SEPARATOR.'dir'.\DIRECTORY_SEPARATOR.'link';
+ $link2 = $this->workspace.\DIRECTORY_SEPARATOR.'dir'.\DIRECTORY_SEPARATOR.'subdir'.\DIRECTORY_SEPARATOR.'link';
+
+ touch($file);
+
+ $this->filesystem->symlink($file, $link1);
+ $this->filesystem->symlink($file, $link2);
+
+ $this->assertTrue(is_link($link1));
+ $this->assertEquals($file, readlink($link1));
+ $this->assertTrue(is_link($link2));
+ $this->assertEquals($file, readlink($link2));
+ }
+
+ public function testLink()
+ {
+ $this->markAsSkippedIfLinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ touch($file);
+ $this->filesystem->hardlink($file, $link);
+
+ $this->assertTrue(is_file($link));
+ $this->assertEquals(fileinode($file), fileinode($link));
+ }
+
+ /**
+ * @depends testLink
+ */
+ public function testRemoveLink()
+ {
+ $this->markAsSkippedIfLinkIsMissing();
+
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ $this->filesystem->remove($link);
+
+ $this->assertTrue(!is_file($link));
+ }
+
+ public function testLinkIsOverwrittenIfPointsToDifferentTarget()
+ {
+ $this->markAsSkippedIfLinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $file2 = $this->workspace.\DIRECTORY_SEPARATOR.'file2';
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ touch($file);
+ touch($file2);
+ link($file2, $link);
+
+ $this->filesystem->hardlink($file, $link);
+
+ $this->assertTrue(is_file($link));
+ $this->assertEquals(fileinode($file), fileinode($link));
+ }
+
+ public function testLinkIsNotOverwrittenIfAlreadyCreated()
+ {
+ $this->markAsSkippedIfLinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ touch($file);
+ link($file, $link);
+
+ $this->filesystem->hardlink($file, $link);
+
+ $this->assertTrue(is_file($link));
+ $this->assertEquals(fileinode($file), fileinode($link));
+ }
+
+ public function testLinkWithSeveralTargets()
+ {
+ $this->markAsSkippedIfLinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link1 = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+ $link2 = $this->workspace.\DIRECTORY_SEPARATOR.'link2';
+
+ touch($file);
+
+ $this->filesystem->hardlink($file, [$link1, $link2]);
+
+ $this->assertTrue(is_file($link1));
+ $this->assertEquals(fileinode($file), fileinode($link1));
+ $this->assertTrue(is_file($link2));
+ $this->assertEquals(fileinode($file), fileinode($link2));
+ }
+
+ public function testLinkWithSameTarget()
+ {
+ $this->markAsSkippedIfLinkIsMissing();
+
+ $file = $this->workspace.\DIRECTORY_SEPARATOR.'file';
+ $link = $this->workspace.\DIRECTORY_SEPARATOR.'link';
+
+ touch($file);
+
+ // practically same as testLinkIsNotOverwrittenIfAlreadyCreated
+ $this->filesystem->hardlink($file, [$link, $link]);
+
+ $this->assertTrue(is_file($link));
+ $this->assertEquals(fileinode($file), fileinode($link));
+ }
+
+ public function testReadRelativeLink()
+ {
+ $this->markAsSkippedIfSymlinkIsMissing();
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Relative symbolic links are not supported on Windows');
+ }
+
+ $file = $this->workspace.'/file';
+ $link1 = $this->workspace.'/dir/link';
+ $link2 = $this->workspace.'/dir/link2';
+ touch($file);
+
+ $this->filesystem->symlink('../file', $link1);
+ $this->filesystem->symlink('link', $link2);
+
+ $this->assertEquals($this->normalize('../file'), $this->filesystem->readlink($link1));
+ $this->assertEquals('link', $this->filesystem->readlink($link2));
+ $this->assertEquals($file, $this->filesystem->readlink($link1, true));
+ $this->assertEquals($file, $this->filesystem->readlink($link2, true));
+ $this->assertEquals($file, $this->filesystem->readlink($file, true));
+ }
+
+ public function testReadAbsoluteLink()
+ {
+ $this->markAsSkippedIfSymlinkIsMissing();
+
+ $file = $this->normalize($this->workspace.'/file');
+ $link1 = $this->normalize($this->workspace.'/dir/link');
+ $link2 = $this->normalize($this->workspace.'/dir/link2');
+ touch($file);
+
+ $this->filesystem->symlink($file, $link1);
+ $this->filesystem->symlink($link1, $link2);
+
+ $this->assertEquals($file, $this->filesystem->readlink($link1));
+ $this->assertEquals($link1, $this->filesystem->readlink($link2));
+ $this->assertEquals($file, $this->filesystem->readlink($link1, true));
+ $this->assertEquals($file, $this->filesystem->readlink($link2, true));
+ $this->assertEquals($file, $this->filesystem->readlink($file, true));
+ }
+
+ public function testReadBrokenLink()
+ {
+ $this->markAsSkippedIfSymlinkIsMissing();
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support creating "broken" symlinks');
+ }
+
+ $file = $this->workspace.'/file';
+ $link = $this->workspace.'/link';
+
+ $this->filesystem->symlink($file, $link);
+
+ $this->assertEquals($file, $this->filesystem->readlink($link));
+ $this->assertNull($this->filesystem->readlink($link, true));
+
+ touch($file);
+ $this->assertEquals($file, $this->filesystem->readlink($link, true));
+ }
+
+ public function testReadLinkDefaultPathDoesNotExist()
+ {
+ $this->assertNull($this->filesystem->readlink($this->normalize($this->workspace.'/invalid')));
+ }
+
+ public function testReadLinkDefaultPathNotLink()
+ {
+ $file = $this->normalize($this->workspace.'/file');
+ touch($file);
+
+ $this->assertNull($this->filesystem->readlink($file));
+ }
+
+ public function testReadLinkCanonicalizePath()
+ {
+ $this->markAsSkippedIfSymlinkIsMissing();
+
+ $file = $this->normalize($this->workspace.'/file');
+ mkdir($this->normalize($this->workspace.'/dir'));
+ touch($file);
+
+ $this->assertEquals($file, $this->filesystem->readlink($this->normalize($this->workspace.'/dir/../file'), true));
+ }
+
+ public function testReadLinkCanonicalizedPathDoesNotExist()
+ {
+ $this->assertNull($this->filesystem->readlink($this->normalize($this->workspace.'invalid'), true));
+ }
+
+ /**
+ * @dataProvider providePathsForMakePathRelative
+ */
+ public function testMakePathRelative($endPath, $startPath, $expectedPath)
+ {
+ $path = $this->filesystem->makePathRelative($endPath, $startPath);
+
+ $this->assertEquals($expectedPath, $path);
+ }
+
+ public function providePathsForMakePathRelative()
+ {
+ $paths = [
+ ['/var/lib/symfony/src/Symfony/', '/var/lib/symfony/src/Symfony/Component', '../'],
+ ['/var/lib/symfony/src/Symfony/', '/var/lib/symfony/src/Symfony/Component/', '../'],
+ ['/var/lib/symfony/src/Symfony', '/var/lib/symfony/src/Symfony/Component', '../'],
+ ['/var/lib/symfony/src/Symfony', '/var/lib/symfony/src/Symfony/Component/', '../'],
+ ['/usr/lib/symfony/', '/var/lib/symfony/src/Symfony/Component', '../../../../../../usr/lib/symfony/'],
+ ['/var/lib/symfony/src/Symfony/', '/var/lib/symfony/', 'src/Symfony/'],
+ ['/aa/bb', '/aa/bb', './'],
+ ['/aa/bb', '/aa/bb/', './'],
+ ['/aa/bb/', '/aa/bb', './'],
+ ['/aa/bb/', '/aa/bb/', './'],
+ ['/aa/bb/cc', '/aa/bb/cc/dd', '../'],
+ ['/aa/bb/cc', '/aa/bb/cc/dd/', '../'],
+ ['/aa/bb/cc/', '/aa/bb/cc/dd', '../'],
+ ['/aa/bb/cc/', '/aa/bb/cc/dd/', '../'],
+ ['/aa/bb/cc', '/aa', 'bb/cc/'],
+ ['/aa/bb/cc', '/aa/', 'bb/cc/'],
+ ['/aa/bb/cc/', '/aa', 'bb/cc/'],
+ ['/aa/bb/cc/', '/aa/', 'bb/cc/'],
+ ['/a/aab/bb', '/a/aa', '../aab/bb/'],
+ ['/a/aab/bb', '/a/aa/', '../aab/bb/'],
+ ['/a/aab/bb/', '/a/aa', '../aab/bb/'],
+ ['/a/aab/bb/', '/a/aa/', '../aab/bb/'],
+ ['/a/aab/bb/', '/', 'a/aab/bb/'],
+ ['/a/aab/bb/', '/b/aab', '../../a/aab/bb/'],
+ ['/aab/bb', '/aa', '../aab/bb/'],
+ ['/aab', '/aa', '../aab/'],
+ ['/aa/bb/cc', '/aa/dd/..', 'bb/cc/'],
+ ['/aa/../bb/cc', '/aa/dd/..', '../bb/cc/'],
+ ['/aa/bb/../../cc', '/aa/../dd/..', 'cc/'],
+ ['/../aa/bb/cc', '/aa/dd/..', 'bb/cc/'],
+ ['/../../aa/../bb/cc', '/aa/dd/..', '../bb/cc/'],
+ ['C:/aa/bb/cc', 'C:/aa/dd/..', 'bb/cc/'],
+ ['c:/aa/../bb/cc', 'c:/aa/dd/..', '../bb/cc/'],
+ ['C:/aa/bb/../../cc', 'C:/aa/../dd/..', 'cc/'],
+ ['C:/../aa/bb/cc', 'C:/aa/dd/..', 'bb/cc/'],
+ ['C:/../../aa/../bb/cc', 'C:/aa/dd/..', '../bb/cc/'],
+ ];
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $paths[] = ['c:\var\lib/symfony/src/Symfony/', 'c:/var/lib/symfony/', 'src/Symfony/'];
+ }
+
+ return $paths;
+ }
+
+ /**
+ * @group legacy
+ * @dataProvider provideLegacyPathsForMakePathRelativeWithRelativePaths
+ * @expectedDeprecation Support for passing relative paths to Symfony\Component\Filesystem\Filesystem::makePathRelative() is deprecated since Symfony 3.4 and will be removed in 4.0.
+ */
+ public function testMakePathRelativeWithRelativePaths($endPath, $startPath, $expectedPath)
+ {
+ $path = $this->filesystem->makePathRelative($endPath, $startPath);
+
+ $this->assertEquals($expectedPath, $path);
+ }
+
+ public function provideLegacyPathsForMakePathRelativeWithRelativePaths()
+ {
+ return [
+ ['usr/lib/symfony/', 'var/lib/symfony/src/Symfony/Component', '../../../../../../usr/lib/symfony/'],
+ ['aa/bb', 'aa/cc', '../bb/'],
+ ['aa/cc', 'bb/cc', '../../aa/cc/'],
+ ['aa/bb', 'aa/./cc', '../bb/'],
+ ['aa/./bb', 'aa/cc', '../bb/'],
+ ['aa/./bb', 'aa/./cc', '../bb/'],
+ ['../../', '../../', './'],
+ ['../aa/bb/', 'aa/bb/', '../../../aa/bb/'],
+ ['../../../', '../../', '../'],
+ ['', '', './'],
+ ['', 'aa/', '../'],
+ ['aa/', '', 'aa/'],
+ ];
+ }
+
+ public function testMirrorCopiesFilesAndDirectoriesRecursively()
+ {
+ $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR;
+ $directory = $sourcePath.'directory'.\DIRECTORY_SEPARATOR;
+ $file1 = $directory.'file1';
+ $file2 = $sourcePath.'file2';
+
+ mkdir($sourcePath);
+ mkdir($directory);
+ file_put_contents($file1, 'FILE1');
+ file_put_contents($file2, 'FILE2');
+
+ $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR;
+
+ $this->filesystem->mirror($sourcePath, $targetPath);
+
+ $this->assertDirectoryExists($targetPath);
+ $this->assertDirectoryExists($targetPath.'directory');
+ $this->assertFileEquals($file1, $targetPath.'directory'.\DIRECTORY_SEPARATOR.'file1');
+ $this->assertFileEquals($file2, $targetPath.'file2');
+
+ $this->filesystem->remove($file1);
+
+ $this->filesystem->mirror($sourcePath, $targetPath, null, ['delete' => false]);
+ $this->assertTrue($this->filesystem->exists($targetPath.'directory'.\DIRECTORY_SEPARATOR.'file1'));
+
+ $this->filesystem->mirror($sourcePath, $targetPath, null, ['delete' => true]);
+ $this->assertFalse($this->filesystem->exists($targetPath.'directory'.\DIRECTORY_SEPARATOR.'file1'));
+
+ file_put_contents($file1, 'FILE1');
+
+ $this->filesystem->mirror($sourcePath, $targetPath, null, ['delete' => true]);
+ $this->assertTrue($this->filesystem->exists($targetPath.'directory'.\DIRECTORY_SEPARATOR.'file1'));
+
+ $this->filesystem->remove($directory);
+ $this->filesystem->mirror($sourcePath, $targetPath, null, ['delete' => true]);
+ $this->assertFalse($this->filesystem->exists($targetPath.'directory'));
+ $this->assertFalse($this->filesystem->exists($targetPath.'directory'.\DIRECTORY_SEPARATOR.'file1'));
+ }
+
+ public function testMirrorCreatesEmptyDirectory()
+ {
+ $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR;
+
+ mkdir($sourcePath);
+
+ $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR;
+
+ $this->filesystem->mirror($sourcePath, $targetPath);
+
+ $this->assertDirectoryExists($targetPath);
+
+ $this->filesystem->remove($sourcePath);
+ }
+
+ public function testMirrorCopiesLinks()
+ {
+ $this->markAsSkippedIfSymlinkIsMissing();
+
+ $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR;
+
+ mkdir($sourcePath);
+ file_put_contents($sourcePath.'file1', 'FILE1');
+ symlink($sourcePath.'file1', $sourcePath.'link1');
+
+ $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR;
+
+ $this->filesystem->mirror($sourcePath, $targetPath);
+
+ $this->assertDirectoryExists($targetPath);
+ $this->assertFileEquals($sourcePath.'file1', $targetPath.'link1');
+ $this->assertTrue(is_link($targetPath.\DIRECTORY_SEPARATOR.'link1'));
+ }
+
+ public function testMirrorCopiesLinkedDirectoryContents()
+ {
+ $this->markAsSkippedIfSymlinkIsMissing(true);
+
+ $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR;
+
+ mkdir($sourcePath.'nested/', 0777, true);
+ file_put_contents($sourcePath.'/nested/file1.txt', 'FILE1');
+ // Note: We symlink directory, not file
+ symlink($sourcePath.'nested', $sourcePath.'link1');
+
+ $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR;
+
+ $this->filesystem->mirror($sourcePath, $targetPath);
+
+ $this->assertDirectoryExists($targetPath);
+ $this->assertFileEquals($sourcePath.'/nested/file1.txt', $targetPath.'link1/file1.txt');
+ $this->assertTrue(is_link($targetPath.\DIRECTORY_SEPARATOR.'link1'));
+ }
+
+ public function testMirrorCopiesRelativeLinkedContents()
+ {
+ $this->markAsSkippedIfSymlinkIsMissing(true);
+
+ $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR;
+ $oldPath = getcwd();
+
+ mkdir($sourcePath.'nested/', 0777, true);
+ file_put_contents($sourcePath.'/nested/file1.txt', 'FILE1');
+ // Note: Create relative symlink
+ chdir($sourcePath);
+ symlink('nested', 'link1');
+
+ chdir($oldPath);
+
+ $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR;
+
+ $this->filesystem->mirror($sourcePath, $targetPath);
+
+ $this->assertDirectoryExists($targetPath);
+ $this->assertFileEquals($sourcePath.'/nested/file1.txt', $targetPath.'link1/file1.txt');
+ $this->assertTrue(is_link($targetPath.\DIRECTORY_SEPARATOR.'link1'));
+ $this->assertEquals('\\' === \DIRECTORY_SEPARATOR ? realpath($sourcePath.'\nested') : 'nested', readlink($targetPath.\DIRECTORY_SEPARATOR.'link1'));
+ }
+
+ public function testMirrorContentsWithSameNameAsSourceOrTargetWithoutDeleteOption()
+ {
+ $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR;
+
+ mkdir($sourcePath);
+ touch($sourcePath.'source');
+ touch($sourcePath.'target');
+
+ $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR;
+
+ $oldPath = getcwd();
+ chdir($this->workspace);
+
+ $this->filesystem->mirror('source', $targetPath);
+
+ chdir($oldPath);
+
+ $this->assertDirectoryExists($targetPath);
+ $this->assertFileExists($targetPath.'source');
+ $this->assertFileExists($targetPath.'target');
+ }
+
+ public function testMirrorContentsWithSameNameAsSourceOrTargetWithDeleteOption()
+ {
+ $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR;
+
+ mkdir($sourcePath);
+ touch($sourcePath.'source');
+
+ $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR;
+
+ mkdir($targetPath);
+ touch($targetPath.'source');
+ touch($targetPath.'target');
+
+ $oldPath = getcwd();
+ chdir($this->workspace);
+
+ $this->filesystem->mirror('source', 'target', null, ['delete' => true]);
+
+ chdir($oldPath);
+
+ $this->assertDirectoryExists($targetPath);
+ $this->assertFileExists($targetPath.'source');
+ $this->assertFileNotExists($targetPath.'target');
+ }
+
+ public function testMirrorFromSubdirectoryInToParentDirectory()
+ {
+ $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR;
+ $sourcePath = $targetPath.'bar'.\DIRECTORY_SEPARATOR;
+ $file1 = $sourcePath.'file1';
+ $file2 = $sourcePath.'file2';
+
+ $this->filesystem->mkdir($sourcePath);
+ file_put_contents($file1, 'FILE1');
+ file_put_contents($file2, 'FILE2');
+
+ $this->filesystem->mirror($sourcePath, $targetPath);
+
+ $this->assertFileEquals($file1, $targetPath.'file1');
+ }
+
+ /**
+ * @dataProvider providePathsForIsAbsolutePath
+ */
+ public function testIsAbsolutePath($path, $expectedResult)
+ {
+ $result = $this->filesystem->isAbsolutePath($path);
+
+ $this->assertEquals($expectedResult, $result);
+ }
+
+ public function providePathsForIsAbsolutePath()
+ {
+ return [
+ ['/var/lib', true],
+ ['c:\\\\var\\lib', true],
+ ['\\var\\lib', true],
+ ['var/lib', false],
+ ['../var/lib', false],
+ ['', false],
+ [null, false],
+ ];
+ }
+
+ public function testTempnam()
+ {
+ $dirname = $this->workspace;
+
+ $filename = $this->filesystem->tempnam($dirname, 'foo');
+
+ $this->assertFileExists($filename);
+ }
+
+ public function testTempnamWithFileScheme()
+ {
+ $scheme = 'file://';
+ $dirname = $scheme.$this->workspace;
+
+ $filename = $this->filesystem->tempnam($dirname, 'foo');
+
+ $this->assertStringStartsWith($scheme, $filename);
+ $this->assertFileExists($filename);
+ }
+
+ public function testTempnamWithMockScheme()
+ {
+ stream_wrapper_register('mock', 'Symfony\Component\Filesystem\Tests\Fixtures\MockStream\MockStream');
+
+ $scheme = 'mock://';
+ $dirname = $scheme.$this->workspace;
+
+ $filename = $this->filesystem->tempnam($dirname, 'foo');
+
+ $this->assertStringStartsWith($scheme, $filename);
+ $this->assertFileExists($filename);
+ }
+
+ public function testTempnamWithZlibSchemeFails()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ $scheme = 'compress.zlib://';
+ $dirname = $scheme.$this->workspace;
+
+ // The compress.zlib:// stream does not support mode x: creates the file, errors "failed to open stream: operation failed" and returns false
+ $this->filesystem->tempnam($dirname, 'bar');
+ }
+
+ public function testTempnamWithPHPTempSchemeFails()
+ {
+ $scheme = 'php://temp';
+ $dirname = $scheme;
+
+ $filename = $this->filesystem->tempnam($dirname, 'bar');
+
+ $this->assertStringStartsWith($scheme, $filename);
+
+ // The php://temp stream deletes the file after close
+ $this->assertFileNotExists($filename);
+ }
+
+ public function testTempnamWithPharSchemeFails()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ // Skip test if Phar disabled phar.readonly must be 0 in php.ini
+ if (!\Phar::canWrite()) {
+ $this->markTestSkipped('This test cannot run when phar.readonly is 1.');
+ }
+
+ $scheme = 'phar://';
+ $dirname = $scheme.$this->workspace;
+ $pharname = 'foo.phar';
+
+ new \Phar($this->workspace.'/'.$pharname, 0, $pharname);
+ // The phar:// stream does not support mode x: fails to create file, errors "failed to open stream: phar error: "$filename" is not a file in phar "$pharname"" and returns false
+ $this->filesystem->tempnam($dirname, $pharname.'/bar');
+ }
+
+ public function testTempnamWithHTTPSchemeFails()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ $scheme = 'http://';
+ $dirname = $scheme.$this->workspace;
+
+ // The http:// scheme is read-only
+ $this->filesystem->tempnam($dirname, 'bar');
+ }
+
+ public function testTempnamOnUnwritableFallsBackToSysTmp()
+ {
+ $scheme = 'file://';
+ $dirname = $scheme.$this->workspace.\DIRECTORY_SEPARATOR.'does_not_exist';
+
+ $filename = $this->filesystem->tempnam($dirname, 'bar');
+ $realTempDir = realpath(sys_get_temp_dir());
+ $this->assertStringStartsWith(rtrim($scheme.$realTempDir, \DIRECTORY_SEPARATOR), $filename);
+ $this->assertFileExists($filename);
+
+ // Tear down
+ @unlink($filename);
+ }
+
+ public function testDumpFile()
+ {
+ $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt';
+
+ // skip mode check on Windows
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ $oldMask = umask(0002);
+ }
+
+ $this->filesystem->dumpFile($filename, 'bar');
+ $this->assertFileExists($filename);
+ $this->assertStringEqualsFile($filename, 'bar');
+
+ // skip mode check on Windows
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ $this->assertFilePermissions(664, $filename);
+ umask($oldMask);
+ }
+ }
+
+ public function testDumpFileWithArray()
+ {
+ $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt';
+
+ $this->filesystem->dumpFile($filename, ['bar']);
+
+ $this->assertFileExists($filename);
+ $this->assertStringEqualsFile($filename, 'bar');
+ }
+
+ public function testDumpFileWithResource()
+ {
+ $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt';
+
+ $resource = fopen('php://memory', 'rw');
+ fwrite($resource, 'bar');
+ fseek($resource, 0);
+
+ $this->filesystem->dumpFile($filename, $resource);
+
+ fclose($resource);
+ $this->assertFileExists($filename);
+ $this->assertStringEqualsFile($filename, 'bar');
+ }
+
+ public function testDumpFileOverwritesAnExistingFile()
+ {
+ $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo.txt';
+ file_put_contents($filename, 'FOO BAR');
+
+ $this->filesystem->dumpFile($filename, 'bar');
+
+ $this->assertFileExists($filename);
+ $this->assertStringEqualsFile($filename, 'bar');
+ }
+
+ public function testDumpFileWithFileScheme()
+ {
+ if (\defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not handle the file:// scheme correctly');
+ }
+
+ $scheme = 'file://';
+ $filename = $scheme.$this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt';
+
+ $this->filesystem->dumpFile($filename, 'bar');
+
+ $this->assertFileExists($filename);
+ $this->assertStringEqualsFile($filename, 'bar');
+ }
+
+ public function testDumpFileWithZlibScheme()
+ {
+ $scheme = 'compress.zlib://';
+ $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt';
+
+ $this->filesystem->dumpFile($filename, 'bar');
+
+ // Zlib stat uses file:// wrapper so remove scheme
+ $this->assertFileExists(str_replace($scheme, '', $filename));
+ $this->assertStringEqualsFile($filename, 'bar');
+ }
+
+ public function testAppendToFile()
+ {
+ $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'bar.txt';
+
+ // skip mode check on Windows
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ $oldMask = umask(0002);
+ }
+
+ $this->filesystem->dumpFile($filename, 'foo');
+
+ $this->filesystem->appendToFile($filename, 'bar');
+
+ $this->assertFileExists($filename);
+ $this->assertStringEqualsFile($filename, 'foobar');
+
+ // skip mode check on Windows
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ $this->assertFilePermissions(664, $filename);
+ umask($oldMask);
+ }
+ }
+
+ public function testAppendToFileWithScheme()
+ {
+ if (\defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not handle the file:// scheme correctly');
+ }
+
+ $scheme = 'file://';
+ $filename = $scheme.$this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt';
+ $this->filesystem->dumpFile($filename, 'foo');
+
+ $this->filesystem->appendToFile($filename, 'bar');
+
+ $this->assertFileExists($filename);
+ $this->assertStringEqualsFile($filename, 'foobar');
+ }
+
+ public function testAppendToFileWithZlibScheme()
+ {
+ $scheme = 'compress.zlib://';
+ $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt';
+ $this->filesystem->dumpFile($filename, 'foo');
+
+ // Zlib stat uses file:// wrapper so remove it
+ $this->assertStringEqualsFile(str_replace($scheme, '', $filename), 'foo');
+
+ $this->filesystem->appendToFile($filename, 'bar');
+
+ $this->assertFileExists($filename);
+ $this->assertStringEqualsFile($filename, 'foobar');
+ }
+
+ public function testAppendToFileCreateTheFileIfNotExists()
+ {
+ $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'bar.txt';
+
+ // skip mode check on Windows
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ $oldMask = umask(0002);
+ }
+
+ $this->filesystem->appendToFile($filename, 'bar');
+
+ // skip mode check on Windows
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ $this->assertFilePermissions(664, $filename);
+ umask($oldMask);
+ }
+
+ $this->assertFileExists($filename);
+ $this->assertStringEqualsFile($filename, 'bar');
+ }
+
+ public function testDumpKeepsExistingPermissionsWhenOverwritingAnExistingFile()
+ {
+ $this->markAsSkippedIfChmodIsMissing();
+
+ $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo.txt';
+ file_put_contents($filename, 'FOO BAR');
+ chmod($filename, 0745);
+
+ $this->filesystem->dumpFile($filename, 'bar', null);
+
+ $this->assertFilePermissions(745, $filename);
+ }
+
+ public function testCopyShouldKeepExecutionPermission()
+ {
+ $this->markAsSkippedIfChmodIsMissing();
+
+ $sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
+ $targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
+
+ file_put_contents($sourceFilePath, 'SOURCE FILE');
+ chmod($sourceFilePath, 0745);
+
+ $this->filesystem->copy($sourceFilePath, $targetFilePath);
+
+ $this->assertFilePermissions(767, $targetFilePath);
+ }
+
+ /**
+ * Normalize the given path (transform each blackslash into a real directory separator).
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ private function normalize($path)
+ {
+ return str_replace('/', \DIRECTORY_SEPARATOR, $path);
+ }
+}
diff --git a/console/skel/symfony/filesystem/Tests/FilesystemTestCase.php b/console/skel/symfony/filesystem/Tests/FilesystemTestCase.php
new file mode 100644
index 0000000..6fb9eba
--- /dev/null
+++ b/console/skel/symfony/filesystem/Tests/FilesystemTestCase.php
@@ -0,0 +1,165 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Filesystem\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Filesystem\Filesystem;
+
+class FilesystemTestCase extends TestCase
+{
+ private $umask;
+
+ protected $longPathNamesWindows = [];
+
+ /**
+ * @var Filesystem
+ */
+ protected $filesystem = null;
+
+ /**
+ * @var string
+ */
+ protected $workspace = null;
+
+ /**
+ * @var bool|null Flag for hard links on Windows
+ */
+ private static $linkOnWindows = null;
+
+ /**
+ * @var bool|null Flag for symbolic links on Windows
+ */
+ private static $symlinkOnWindows = null;
+
+ public static function setUpBeforeClass()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ self::$linkOnWindows = true;
+ $originFile = tempnam(sys_get_temp_dir(), 'li');
+ $targetFile = tempnam(sys_get_temp_dir(), 'li');
+ if (true !== @link($originFile, $targetFile)) {
+ $report = error_get_last();
+ if (\is_array($report) && false !== strpos($report['message'], 'error code(1314)')) {
+ self::$linkOnWindows = false;
+ }
+ } else {
+ @unlink($targetFile);
+ }
+
+ self::$symlinkOnWindows = true;
+ $originDir = tempnam(sys_get_temp_dir(), 'sl');
+ $targetDir = tempnam(sys_get_temp_dir(), 'sl');
+ if (true !== @symlink($originDir, $targetDir)) {
+ $report = error_get_last();
+ if (\is_array($report) && false !== strpos($report['message'], 'error code(1314)')) {
+ self::$symlinkOnWindows = false;
+ }
+ } else {
+ @unlink($targetDir);
+ }
+ }
+ }
+
+ protected function setUp()
+ {
+ $this->umask = umask(0);
+ $this->filesystem = new Filesystem();
+ $this->workspace = sys_get_temp_dir().'/'.microtime(true).'.'.mt_rand();
+ mkdir($this->workspace, 0777, true);
+ $this->workspace = realpath($this->workspace);
+ }
+
+ protected function tearDown()
+ {
+ if (!empty($this->longPathNamesWindows)) {
+ foreach ($this->longPathNamesWindows as $path) {
+ exec('DEL '.$path);
+ }
+ $this->longPathNamesWindows = [];
+ }
+
+ $this->filesystem->remove($this->workspace);
+ umask($this->umask);
+ }
+
+ /**
+ * @param int $expectedFilePerms Expected file permissions as three digits (i.e. 755)
+ * @param string $filePath
+ */
+ protected function assertFilePermissions($expectedFilePerms, $filePath)
+ {
+ $actualFilePerms = (int) substr(sprintf('%o', fileperms($filePath)), -3);
+ $this->assertEquals(
+ $expectedFilePerms,
+ $actualFilePerms,
+ sprintf('File permissions for %s must be %s. Actual %s', $filePath, $expectedFilePerms, $actualFilePerms)
+ );
+ }
+
+ protected function getFileOwner($filepath)
+ {
+ $this->markAsSkippedIfPosixIsMissing();
+
+ $infos = stat($filepath);
+
+ return ($datas = posix_getpwuid($infos['uid'])) ? $datas['name'] : null;
+ }
+
+ protected function getFileGroup($filepath)
+ {
+ $this->markAsSkippedIfPosixIsMissing();
+
+ $infos = stat($filepath);
+ if ($datas = posix_getgrgid($infos['gid'])) {
+ return $datas['name'];
+ }
+
+ $this->markTestSkipped('Unable to retrieve file group name');
+ }
+
+ protected function markAsSkippedIfLinkIsMissing()
+ {
+ if (!\function_exists('link')) {
+ $this->markTestSkipped('link is not supported');
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR && false === self::$linkOnWindows) {
+ $this->markTestSkipped('link requires "Create hard links" privilege on windows');
+ }
+ }
+
+ protected function markAsSkippedIfSymlinkIsMissing($relative = false)
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR && false === self::$symlinkOnWindows) {
+ $this->markTestSkipped('symlink requires "Create symbolic links" privilege on Windows');
+ }
+
+ // https://bugs.php.net/69473
+ if ($relative && '\\' === \DIRECTORY_SEPARATOR && 1 === PHP_ZTS) {
+ $this->markTestSkipped('symlink does not support relative paths on thread safe Windows PHP versions');
+ }
+ }
+
+ protected function markAsSkippedIfChmodIsMissing()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('chmod is not supported on Windows');
+ }
+ }
+
+ protected function markAsSkippedIfPosixIsMissing()
+ {
+ if (!\function_exists('posix_isatty')) {
+ $this->markTestSkipped('Function posix_isatty is required.');
+ }
+ }
+}
diff --git a/console/skel/symfony/filesystem/Tests/Fixtures/MockStream/MockStream.php b/console/skel/symfony/filesystem/Tests/Fixtures/MockStream/MockStream.php
new file mode 100644
index 0000000..3d80a90
--- /dev/null
+++ b/console/skel/symfony/filesystem/Tests/Fixtures/MockStream/MockStream.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Filesystem\Tests\Fixtures\MockStream;
+
+/**
+ * Mock stream class to be used with stream_wrapper_register.
+ * stream_wrapper_register('mock', 'Symfony\Component\Filesystem\Tests\Fixtures\MockStream\MockStream').
+ */
+class MockStream
+{
+ /**
+ * Opens file or URL.
+ *
+ * @param string $path Specifies the URL that was passed to the original function
+ * @param string $mode The mode used to open the file, as detailed for fopen()
+ * @param int $options Holds additional flags set by the streams API
+ * @param string $opened_path If the path is opened successfully, and STREAM_USE_PATH is set in options,
+ * opened_path should be set to the full path of the file/resource that was actually opened
+ *
+ * @return bool
+ */
+ public function stream_open($path, $mode, $options, &$opened_path)
+ {
+ return true;
+ }
+
+ /**
+ * @param string $path The file path or URL to stat
+ * @param array $flags Holds additional flags set by the streams API
+ *
+ * @return array File stats
+ */
+ public function url_stat($path, $flags)
+ {
+ return [];
+ }
+}
diff --git a/console/skel/symfony/filesystem/Tests/LockHandlerTest.php b/console/skel/symfony/filesystem/Tests/LockHandlerTest.php
new file mode 100644
index 0000000..d130803
--- /dev/null
+++ b/console/skel/symfony/filesystem/Tests/LockHandlerTest.php
@@ -0,0 +1,144 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Filesystem\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Filesystem\Exception\IOException;
+use Symfony\Component\Filesystem\Filesystem;
+use Symfony\Component\Filesystem\LockHandler;
+
+/**
+ * @group legacy
+ */
+class LockHandlerTest extends TestCase
+{
+ public function testConstructWhenRepositoryDoesNotExist()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ $this->expectExceptionMessage('Failed to create "/a/b/c/d/e": mkdir(): Permission denied.');
+ if (!getenv('USER') || 'root' === getenv('USER')) {
+ $this->markTestSkipped('This test will fail if run under superuser');
+ }
+ new LockHandler('lock', '/a/b/c/d/e');
+ }
+
+ public function testConstructWhenRepositoryIsNotWriteable()
+ {
+ $this->expectException('Symfony\Component\Filesystem\Exception\IOException');
+ $this->expectExceptionMessage('The directory "/" is not writable.');
+ if (!getenv('USER') || 'root' === getenv('USER')) {
+ $this->markTestSkipped('This test will fail if run under superuser');
+ }
+ new LockHandler('lock', '/');
+ }
+
+ public function testErrorHandlingInLockIfLockPathBecomesUnwritable()
+ {
+ // skip test on Windows; PHP can't easily set file as unreadable on Windows
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('This test cannot run on Windows.');
+ }
+
+ if (!getenv('USER') || 'root' === getenv('USER')) {
+ $this->markTestSkipped('This test will fail if run under superuser');
+ }
+
+ $lockPath = sys_get_temp_dir().'/'.uniqid('', true);
+ $e = null;
+ $wrongMessage = null;
+
+ try {
+ mkdir($lockPath);
+
+ $lockHandler = new LockHandler('lock', $lockPath);
+
+ chmod($lockPath, 0444);
+
+ $lockHandler->lock();
+ } catch (IOException $e) {
+ if (false === strpos($e->getMessage(), 'Permission denied')) {
+ $wrongMessage = $e->getMessage();
+ } else {
+ $this->addToAssertionCount(1);
+ }
+ } catch (\Exception $e) {
+ } catch (\Throwable $e) {
+ }
+
+ if (is_dir($lockPath)) {
+ $fs = new Filesystem();
+ $fs->remove($lockPath);
+ }
+
+ $this->assertInstanceOf('Symfony\Component\Filesystem\Exception\IOException', $e, sprintf('Expected IOException to be thrown, got %s instead.', \get_class($e)));
+ $this->assertNull($wrongMessage, sprintf('Expected exception message to contain "Permission denied", got "%s" instead.', $wrongMessage));
+ }
+
+ public function testConstructSanitizeName()
+ {
+ $lock = new LockHandler('');
+
+ $file = sprintf('%s/sf.-php-echo-hello-word-.4b3d9d0d27ddef3a78a64685dda3a963e478659a9e5240feaf7b4173a8f28d5f.lock', sys_get_temp_dir());
+ // ensure the file does not exist before the lock
+ @unlink($file);
+
+ $lock->lock();
+
+ $this->assertFileExists($file);
+
+ $lock->release();
+ }
+
+ public function testLockRelease()
+ {
+ $name = 'symfony-test-filesystem.lock';
+
+ $l1 = new LockHandler($name);
+ $l2 = new LockHandler($name);
+
+ $this->assertTrue($l1->lock());
+ $this->assertFalse($l2->lock());
+
+ $l1->release();
+
+ $this->assertTrue($l2->lock());
+ $l2->release();
+ }
+
+ public function testLockTwice()
+ {
+ $name = 'symfony-test-filesystem.lock';
+
+ $lockHandler = new LockHandler($name);
+
+ $this->assertTrue($lockHandler->lock());
+ $this->assertTrue($lockHandler->lock());
+
+ $lockHandler->release();
+ }
+
+ public function testLockIsReleased()
+ {
+ $name = 'symfony-test-filesystem.lock';
+
+ $l1 = new LockHandler($name);
+ $l2 = new LockHandler($name);
+
+ $this->assertTrue($l1->lock());
+ $this->assertFalse($l2->lock());
+
+ $l1 = null;
+
+ $this->assertTrue($l2->lock());
+ $l2->release();
+ }
+}
diff --git a/console/skel/symfony/filesystem/composer.json b/console/skel/symfony/filesystem/composer.json
new file mode 100644
index 0000000..0fc8043
--- /dev/null
+++ b/console/skel/symfony/filesystem/composer.json
@@ -0,0 +1,34 @@
+{
+ "name": "symfony/filesystem",
+ "type": "library",
+ "description": "Symfony Filesystem Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Filesystem\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ }
+}
diff --git a/console/skel/symfony/filesystem/phpunit.xml.dist b/console/skel/symfony/filesystem/phpunit.xml.dist
new file mode 100644
index 0000000..5515fff
--- /dev/null
+++ b/console/skel/symfony/filesystem/phpunit.xml.dist
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/.gitignore b/console/skel/symfony/finder/Symfony/Component/Finder/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/AbstractAdapter.php b/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/AbstractAdapter.php
new file mode 100644
index 0000000..4ddd913
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/AbstractAdapter.php
@@ -0,0 +1,236 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Adapter;
+
+/**
+ * Interface for finder engine implementations.
+ *
+ * @author Jean-François Simon
+ */
+abstract class AbstractAdapter implements AdapterInterface
+{
+ protected $followLinks = false;
+ protected $mode = 0;
+ protected $minDepth = 0;
+ protected $maxDepth = PHP_INT_MAX;
+ protected $exclude = array();
+ protected $names = array();
+ protected $notNames = array();
+ protected $contains = array();
+ protected $notContains = array();
+ protected $sizes = array();
+ protected $dates = array();
+ protected $filters = array();
+ protected $sort = false;
+ protected $paths = array();
+ protected $notPaths = array();
+ protected $ignoreUnreadableDirs = false;
+
+ private static $areSupported = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isSupported()
+ {
+ $name = $this->getName();
+
+ if (!array_key_exists($name, self::$areSupported)) {
+ self::$areSupported[$name] = $this->canBeUsed();
+ }
+
+ return self::$areSupported[$name];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setFollowLinks($followLinks)
+ {
+ $this->followLinks = $followLinks;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setMode($mode)
+ {
+ $this->mode = $mode;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setDepths(array $depths)
+ {
+ $this->minDepth = 0;
+ $this->maxDepth = PHP_INT_MAX;
+
+ foreach ($depths as $comparator) {
+ switch ($comparator->getOperator()) {
+ case '>':
+ $this->minDepth = $comparator->getTarget() + 1;
+ break;
+ case '>=':
+ $this->minDepth = $comparator->getTarget();
+ break;
+ case '<':
+ $this->maxDepth = $comparator->getTarget() - 1;
+ break;
+ case '<=':
+ $this->maxDepth = $comparator->getTarget();
+ break;
+ default:
+ $this->minDepth = $this->maxDepth = $comparator->getTarget();
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setExclude(array $exclude)
+ {
+ $this->exclude = $exclude;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setNames(array $names)
+ {
+ $this->names = $names;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setNotNames(array $notNames)
+ {
+ $this->notNames = $notNames;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setContains(array $contains)
+ {
+ $this->contains = $contains;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setNotContains(array $notContains)
+ {
+ $this->notContains = $notContains;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setSizes(array $sizes)
+ {
+ $this->sizes = $sizes;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setDates(array $dates)
+ {
+ $this->dates = $dates;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setFilters(array $filters)
+ {
+ $this->filters = $filters;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setSort($sort)
+ {
+ $this->sort = $sort;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setPath(array $paths)
+ {
+ $this->paths = $paths;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setNotPath(array $notPaths)
+ {
+ $this->notPaths = $notPaths;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function ignoreUnreadableDirs($ignore = true)
+ {
+ $this->ignoreUnreadableDirs = (bool) $ignore;
+
+ return $this;
+ }
+
+ /**
+ * Returns whether the adapter is supported in the current environment.
+ *
+ * This method should be implemented in all adapters. Do not implement
+ * isSupported in the adapters as the generic implementation provides a cache
+ * layer.
+ *
+ * @see isSupported()
+ *
+ * @return bool Whether the adapter is supported
+ */
+ abstract protected function canBeUsed();
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php b/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php
new file mode 100644
index 0000000..4d73b32
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php
@@ -0,0 +1,327 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Adapter;
+
+use Symfony\Component\Finder\Exception\AccessDeniedException;
+use Symfony\Component\Finder\Iterator;
+use Symfony\Component\Finder\Shell\Shell;
+use Symfony\Component\Finder\Expression\Expression;
+use Symfony\Component\Finder\Shell\Command;
+use Symfony\Component\Finder\Comparator\NumberComparator;
+use Symfony\Component\Finder\Comparator\DateComparator;
+
+/**
+ * Shell engine implementation using GNU find command.
+ *
+ * @author Jean-François Simon
+ */
+abstract class AbstractFindAdapter extends AbstractAdapter
+{
+ /**
+ * @var Shell
+ */
+ protected $shell;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->shell = new Shell();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function searchInDirectory($dir)
+ {
+ // having "/../" in path make find fail
+ $dir = realpath($dir);
+
+ // searching directories containing or not containing strings leads to no result
+ if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode && ($this->contains || $this->notContains)) {
+ return new Iterator\FilePathsIterator(array(), $dir);
+ }
+
+ $command = Command::create();
+ $find = $this->buildFindCommand($command, $dir);
+
+ if ($this->followLinks) {
+ $find->add('-follow');
+ }
+
+ $find->add('-mindepth')->add($this->minDepth + 1);
+
+ if (PHP_INT_MAX !== $this->maxDepth) {
+ $find->add('-maxdepth')->add($this->maxDepth + 1);
+ }
+
+ if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) {
+ $find->add('-type d');
+ } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) {
+ $find->add('-type f');
+ }
+
+ $this->buildNamesFiltering($find, $this->names);
+ $this->buildNamesFiltering($find, $this->notNames, true);
+ $this->buildPathsFiltering($find, $dir, $this->paths);
+ $this->buildPathsFiltering($find, $dir, $this->notPaths, true);
+ $this->buildSizesFiltering($find, $this->sizes);
+ $this->buildDatesFiltering($find, $this->dates);
+
+ $useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs');
+ $useSort = is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('cut');
+
+ if ($useGrep && ($this->contains || $this->notContains)) {
+ $grep = $command->ins('grep');
+ $this->buildContentFiltering($grep, $this->contains);
+ $this->buildContentFiltering($grep, $this->notContains, true);
+ }
+
+ if ($useSort) {
+ $this->buildSorting($command, $this->sort);
+ }
+
+ $command->setErrorHandler(
+ $this->ignoreUnreadableDirs
+ // If directory is unreadable and finder is set to ignore it, `stderr` is ignored.
+ ? function ($stderr) { return; }
+ : function ($stderr) { throw new AccessDeniedException($stderr); }
+ );
+
+ $paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute());
+ $iterator = new Iterator\FilePathsIterator($paths, $dir);
+
+ if ($this->exclude) {
+ $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
+ }
+
+ if (!$useGrep && ($this->contains || $this->notContains)) {
+ $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
+ }
+
+ if ($this->filters) {
+ $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
+ }
+
+ if (!$useSort && $this->sort) {
+ $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
+ $iterator = $iteratorAggregate->getIterator();
+ }
+
+ return $iterator;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function canBeUsed()
+ {
+ return $this->shell->testCommand('find');
+ }
+
+ /**
+ * @param Command $command
+ * @param string $dir
+ *
+ * @return Command
+ */
+ protected function buildFindCommand(Command $command, $dir)
+ {
+ return $command
+ ->ins('find')
+ ->add('find ')
+ ->arg($dir)
+ ->add('-noleaf'); // the -noleaf option is required for filesystems that don't follow the '.' and '..' conventions
+ }
+
+ /**
+ * @param Command $command
+ * @param string[] $names
+ * @param bool $not
+ */
+ private function buildNamesFiltering(Command $command, array $names, $not = false)
+ {
+ if (0 === count($names)) {
+ return;
+ }
+
+ $command->add($not ? '-not' : null)->cmd('(');
+
+ foreach ($names as $i => $name) {
+ $expr = Expression::create($name);
+
+ // Find does not support expandable globs ("*.{a,b}" syntax).
+ if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
+ $expr = Expression::create($expr->getGlob()->toRegex(false));
+ }
+
+ // Fixes 'not search' and 'full path matching' regex problems.
+ // - Jokers '.' are replaced by [^/].
+ // - We add '[^/]*' before and after regex (if no ^|$ flags are present).
+ if ($expr->isRegex()) {
+ $regex = $expr->getRegex();
+ $regex->prepend($regex->hasStartFlag() ? '/' : '/[^/]*')
+ ->setStartFlag(false)
+ ->setStartJoker(true)
+ ->replaceJokers('[^/]');
+ if (!$regex->hasEndFlag() || $regex->hasEndJoker()) {
+ $regex->setEndJoker(false)->append('[^/]*');
+ }
+ }
+
+ $command
+ ->add($i > 0 ? '-or' : null)
+ ->add($expr->isRegex()
+ ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
+ : ($expr->isCaseSensitive() ? '-name' : '-iname')
+ )
+ ->arg($expr->renderPattern());
+ }
+
+ $command->cmd(')');
+ }
+
+ /**
+ * @param Command $command
+ * @param string $dir
+ * @param string[] $paths
+ * @param bool $not
+ */
+ private function buildPathsFiltering(Command $command, $dir, array $paths, $not = false)
+ {
+ if (0 === count($paths)) {
+ return;
+ }
+
+ $command->add($not ? '-not' : null)->cmd('(');
+
+ foreach ($paths as $i => $path) {
+ $expr = Expression::create($path);
+
+ // Find does not support expandable globs ("*.{a,b}" syntax).
+ if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
+ $expr = Expression::create($expr->getGlob()->toRegex(false));
+ }
+
+ // Fixes 'not search' regex problems.
+ if ($expr->isRegex()) {
+ $regex = $expr->getRegex();
+ $regex->prepend($regex->hasStartFlag() ? preg_quote($dir).DIRECTORY_SEPARATOR : '.*')->setEndJoker(!$regex->hasEndFlag());
+ } else {
+ $expr->prepend('*')->append('*');
+ }
+
+ $command
+ ->add($i > 0 ? '-or' : null)
+ ->add($expr->isRegex()
+ ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
+ : ($expr->isCaseSensitive() ? '-path' : '-ipath')
+ )
+ ->arg($expr->renderPattern());
+ }
+
+ $command->cmd(')');
+ }
+
+ /**
+ * @param Command $command
+ * @param NumberComparator[] $sizes
+ */
+ private function buildSizesFiltering(Command $command, array $sizes)
+ {
+ foreach ($sizes as $i => $size) {
+ $command->add($i > 0 ? '-and' : null);
+
+ switch ($size->getOperator()) {
+ case '<=':
+ $command->add('-size -'.($size->getTarget() + 1).'c');
+ break;
+ case '>=':
+ $command->add('-size +'.($size->getTarget() - 1).'c');
+ break;
+ case '>':
+ $command->add('-size +'.$size->getTarget().'c');
+ break;
+ case '!=':
+ $command->add('-size -'.$size->getTarget().'c');
+ $command->add('-size +'.$size->getTarget().'c');
+ break;
+ case '<':
+ default:
+ $command->add('-size -'.$size->getTarget().'c');
+ }
+ }
+ }
+
+ /**
+ * @param Command $command
+ * @param DateComparator[] $dates
+ */
+ private function buildDatesFiltering(Command $command, array $dates)
+ {
+ foreach ($dates as $i => $date) {
+ $command->add($i > 0 ? '-and' : null);
+
+ $mins = (int) round((time()-$date->getTarget()) / 60);
+
+ if (0 > $mins) {
+ // mtime is in the future
+ $command->add(' -mmin -0');
+ // we will have no result so we don't need to continue
+ return;
+ }
+
+ switch ($date->getOperator()) {
+ case '<=':
+ $command->add('-mmin +'.($mins - 1));
+ break;
+ case '>=':
+ $command->add('-mmin -'.($mins + 1));
+ break;
+ case '>':
+ $command->add('-mmin -'.$mins);
+ break;
+ case '!=':
+ $command->add('-mmin +'.$mins.' -or -mmin -'.$mins);
+ break;
+ case '<':
+ default:
+ $command->add('-mmin +'.$mins);
+ }
+ }
+ }
+
+ /**
+ * @param Command $command
+ * @param string $sort
+ *
+ * @throws \InvalidArgumentException
+ */
+ private function buildSorting(Command $command, $sort)
+ {
+ $this->buildFormatSorting($command, $sort);
+ }
+
+ /**
+ * @param Command $command
+ * @param string $sort
+ */
+ abstract protected function buildFormatSorting(Command $command, $sort);
+
+ /**
+ * @param Command $command
+ * @param array $contains
+ * @param bool $not
+ */
+ abstract protected function buildContentFiltering(Command $command, array $contains, $not = false);
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/AdapterInterface.php b/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/AdapterInterface.php
new file mode 100644
index 0000000..bdc3a93
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/AdapterInterface.php
@@ -0,0 +1,144 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Adapter;
+
+/**
+ * @author Jean-François Simon
+ */
+interface AdapterInterface
+{
+ /**
+ * @param bool $followLinks
+ *
+ * @return AdapterInterface Current instance
+ */
+ public function setFollowLinks($followLinks);
+
+ /**
+ * @param int $mode
+ *
+ * @return AdapterInterface Current instance
+ */
+ public function setMode($mode);
+
+ /**
+ * @param array $exclude
+ *
+ * @return AdapterInterface Current instance
+ */
+ public function setExclude(array $exclude);
+
+ /**
+ * @param array $depths
+ *
+ * @return AdapterInterface Current instance
+ */
+ public function setDepths(array $depths);
+
+ /**
+ * @param array $names
+ *
+ * @return AdapterInterface Current instance
+ */
+ public function setNames(array $names);
+
+ /**
+ * @param array $notNames
+ *
+ * @return AdapterInterface Current instance
+ */
+ public function setNotNames(array $notNames);
+
+ /**
+ * @param array $contains
+ *
+ * @return AdapterInterface Current instance
+ */
+ public function setContains(array $contains);
+
+ /**
+ * @param array $notContains
+ *
+ * @return AdapterInterface Current instance
+ */
+ public function setNotContains(array $notContains);
+
+ /**
+ * @param array $sizes
+ *
+ * @return AdapterInterface Current instance
+ */
+ public function setSizes(array $sizes);
+
+ /**
+ * @param array $dates
+ *
+ * @return AdapterInterface Current instance
+ */
+ public function setDates(array $dates);
+
+ /**
+ * @param array $filters
+ *
+ * @return AdapterInterface Current instance
+ */
+ public function setFilters(array $filters);
+
+ /**
+ * @param \Closure|int $sort
+ *
+ * @return AdapterInterface Current instance
+ */
+ public function setSort($sort);
+
+ /**
+ * @param array $paths
+ *
+ * @return AdapterInterface Current instance
+ */
+ public function setPath(array $paths);
+
+ /**
+ * @param array $notPaths
+ *
+ * @return AdapterInterface Current instance
+ */
+ public function setNotPath(array $notPaths);
+
+ /**
+ * @param bool $ignore
+ *
+ * @return AdapterInterface Current instance
+ */
+ public function ignoreUnreadableDirs($ignore = true);
+
+ /**
+ * @param string $dir
+ *
+ * @return \Iterator Result iterator
+ */
+ public function searchInDirectory($dir);
+
+ /**
+ * Tests adapter support for current platform.
+ *
+ * @return bool
+ */
+ public function isSupported();
+
+ /**
+ * Returns adapter name.
+ *
+ * @return string
+ */
+ public function getName();
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/BsdFindAdapter.php b/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/BsdFindAdapter.php
new file mode 100644
index 0000000..4a25bae
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/BsdFindAdapter.php
@@ -0,0 +1,103 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Adapter;
+
+use Symfony\Component\Finder\Shell\Shell;
+use Symfony\Component\Finder\Shell\Command;
+use Symfony\Component\Finder\Iterator\SortableIterator;
+use Symfony\Component\Finder\Expression\Expression;
+
+/**
+ * Shell engine implementation using BSD find command.
+ *
+ * @author Jean-François Simon
+ */
+class BsdFindAdapter extends AbstractFindAdapter
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'bsd_find';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function canBeUsed()
+ {
+ return in_array($this->shell->getType(), array(Shell::TYPE_BSD, Shell::TYPE_DARWIN)) && parent::canBeUsed();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function buildFormatSorting(Command $command, $sort)
+ {
+ switch ($sort) {
+ case SortableIterator::SORT_BY_NAME:
+ $command->ins('sort')->add('| sort');
+
+ return;
+ case SortableIterator::SORT_BY_TYPE:
+ $format = '%HT';
+ break;
+ case SortableIterator::SORT_BY_ACCESSED_TIME:
+ $format = '%a';
+ break;
+ case SortableIterator::SORT_BY_CHANGED_TIME:
+ $format = '%c';
+ break;
+ case SortableIterator::SORT_BY_MODIFIED_TIME:
+ $format = '%m';
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort));
+ }
+
+ $command
+ ->add('-print0 | xargs -0 stat -f')
+ ->arg($format.'%t%N')
+ ->add('| sort | cut -f 2');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function buildFindCommand(Command $command, $dir)
+ {
+ parent::buildFindCommand($command, $dir)->addAtIndex('-E', 1);
+
+ return $command;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function buildContentFiltering(Command $command, array $contains, $not = false)
+ {
+ foreach ($contains as $contain) {
+ $expr = Expression::create($contain);
+
+ // todo: avoid forking process for each $pattern by using multiple -e options
+ $command
+ ->add('| grep -v \'^$\'')
+ ->add('| xargs -I{} grep -I')
+ ->add($expr->isCaseSensitive() ? null : '-i')
+ ->add($not ? '-L' : '-l')
+ ->add('-Ee')->arg($expr->renderPattern())
+ ->add('{}')
+ ;
+ }
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/GnuFindAdapter.php
new file mode 100644
index 0000000..0fbf48f
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/GnuFindAdapter.php
@@ -0,0 +1,104 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Adapter;
+
+use Symfony\Component\Finder\Shell\Shell;
+use Symfony\Component\Finder\Shell\Command;
+use Symfony\Component\Finder\Iterator\SortableIterator;
+use Symfony\Component\Finder\Expression\Expression;
+
+/**
+ * Shell engine implementation using GNU find command.
+ *
+ * @author Jean-François Simon
+ */
+class GnuFindAdapter extends AbstractFindAdapter
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'gnu_find';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function buildFormatSorting(Command $command, $sort)
+ {
+ switch ($sort) {
+ case SortableIterator::SORT_BY_NAME:
+ $command->ins('sort')->add('| sort');
+
+ return;
+ case SortableIterator::SORT_BY_TYPE:
+ $format = '%y';
+ break;
+ case SortableIterator::SORT_BY_ACCESSED_TIME:
+ $format = '%A@';
+ break;
+ case SortableIterator::SORT_BY_CHANGED_TIME:
+ $format = '%C@';
+ break;
+ case SortableIterator::SORT_BY_MODIFIED_TIME:
+ $format = '%T@';
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort));
+ }
+
+ $command
+ ->get('find')
+ ->add('-printf')
+ ->arg($format.' %h/%f\\n')
+ ->add('| sort | cut')
+ ->arg('-d ')
+ ->arg('-f2-')
+ ;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function canBeUsed()
+ {
+ return $this->shell->getType() === Shell::TYPE_UNIX && parent::canBeUsed();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function buildFindCommand(Command $command, $dir)
+ {
+ return parent::buildFindCommand($command, $dir)->add('-regextype posix-extended');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function buildContentFiltering(Command $command, array $contains, $not = false)
+ {
+ foreach ($contains as $contain) {
+ $expr = Expression::create($contain);
+
+ // todo: avoid forking process for each $pattern by using multiple -e options
+ $command
+ ->add('| xargs -I{} -r grep -I')
+ ->add($expr->isCaseSensitive() ? null : '-i')
+ ->add($not ? '-L' : '-l')
+ ->add('-Ee')->arg($expr->renderPattern())
+ ->add('{}')
+ ;
+ }
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/PhpAdapter.php b/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/PhpAdapter.php
new file mode 100644
index 0000000..378a26a
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Adapter/PhpAdapter.php
@@ -0,0 +1,98 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Adapter;
+
+use Symfony\Component\Finder\Iterator;
+
+/**
+ * PHP finder engine implementation.
+ *
+ * @author Jean-François Simon
+ */
+class PhpAdapter extends AbstractAdapter
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function searchInDirectory($dir)
+ {
+ $flags = \RecursiveDirectoryIterator::SKIP_DOTS;
+
+ if ($this->followLinks) {
+ $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
+ }
+
+ $iterator = new \RecursiveIteratorIterator(
+ new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs),
+ \RecursiveIteratorIterator::SELF_FIRST
+ );
+
+ if ($this->minDepth > 0 || $this->maxDepth < PHP_INT_MAX) {
+ $iterator = new Iterator\DepthRangeFilterIterator($iterator, $this->minDepth, $this->maxDepth);
+ }
+
+ if ($this->mode) {
+ $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
+ }
+
+ if ($this->exclude) {
+ $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
+ }
+
+ if ($this->names || $this->notNames) {
+ $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames);
+ }
+
+ if ($this->contains || $this->notContains) {
+ $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
+ }
+
+ if ($this->sizes) {
+ $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes);
+ }
+
+ if ($this->dates) {
+ $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates);
+ }
+
+ if ($this->filters) {
+ $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
+ }
+
+ if ($this->sort) {
+ $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
+ $iterator = $iteratorAggregate->getIterator();
+ }
+
+ if ($this->paths || $this->notPaths) {
+ $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths);
+ }
+
+ return $iterator;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'php';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function canBeUsed()
+ {
+ return true;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/CHANGELOG.md b/console/skel/symfony/finder/Symfony/Component/Finder/CHANGELOG.md
new file mode 100644
index 0000000..f1dd7d5
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/CHANGELOG.md
@@ -0,0 +1,34 @@
+CHANGELOG
+=========
+
+2.5.0
+-----
+ * added support for GLOB_BRACE in the paths passed to Finder::in()
+
+2.3.0
+-----
+
+ * added a way to ignore unreadable directories (via Finder::ignoreUnreadableDirs())
+ * unified the way subfolders that are not executable are handled by always throwing an AccessDeniedException exception
+
+2.2.0
+-----
+
+ * added Finder::path() and Finder::notPath() methods
+ * added finder adapters to improve performance on specific platforms
+ * added support for wildcard characters (glob patterns) in the paths passed
+ to Finder::in()
+
+2.1.0
+-----
+
+ * added Finder::sortByAccessedTime(), Finder::sortByChangedTime(), and
+ Finder::sortByModifiedTime()
+ * added Countable to Finder
+ * added support for an array of directories as an argument to
+ Finder::exclude()
+ * added searching based on the file content via Finder::contains() and
+ Finder::notContains()
+ * added support for the != operator in the Comparator
+ * [BC BREAK] filter expressions (used for file name and content) are no more
+ considered as regexps but glob patterns when they are enclosed in '*' or '?'
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Comparator/Comparator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Comparator/Comparator.php
new file mode 100644
index 0000000..4f5e1ff
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Comparator/Comparator.php
@@ -0,0 +1,98 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Comparator;
+
+/**
+ * Comparator.
+ *
+ * @author Fabien Potencier
+ */
+class Comparator
+{
+ private $target;
+ private $operator = '==';
+
+ /**
+ * Gets the target value.
+ *
+ * @return string The target value
+ */
+ public function getTarget()
+ {
+ return $this->target;
+ }
+
+ /**
+ * Sets the target value.
+ *
+ * @param string $target The target value
+ */
+ public function setTarget($target)
+ {
+ $this->target = $target;
+ }
+
+ /**
+ * Gets the comparison operator.
+ *
+ * @return string The operator
+ */
+ public function getOperator()
+ {
+ return $this->operator;
+ }
+
+ /**
+ * Sets the comparison operator.
+ *
+ * @param string $operator A valid operator
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setOperator($operator)
+ {
+ if (!$operator) {
+ $operator = '==';
+ }
+
+ if (!in_array($operator, array('>', '<', '>=', '<=', '==', '!='))) {
+ throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator));
+ }
+
+ $this->operator = $operator;
+ }
+
+ /**
+ * Tests against the target.
+ *
+ * @param mixed $test A test value
+ *
+ * @return bool
+ */
+ public function test($test)
+ {
+ switch ($this->operator) {
+ case '>':
+ return $test > $this->target;
+ case '>=':
+ return $test >= $this->target;
+ case '<':
+ return $test < $this->target;
+ case '<=':
+ return $test <= $this->target;
+ case '!=':
+ return $test != $this->target;
+ }
+
+ return $test == $this->target;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Comparator/DateComparator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Comparator/DateComparator.php
new file mode 100644
index 0000000..8b7746b
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Comparator/DateComparator.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Comparator;
+
+/**
+ * DateCompare compiles date comparisons.
+ *
+ * @author Fabien Potencier
+ */
+class DateComparator extends Comparator
+{
+ /**
+ * Constructor.
+ *
+ * @param string $test A comparison string
+ *
+ * @throws \InvalidArgumentException If the test is not understood
+ */
+ public function __construct($test)
+ {
+ if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) {
+ throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test));
+ }
+
+ try {
+ $date = new \DateTime($matches[2]);
+ $target = $date->format('U');
+ } catch (\Exception $e) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2]));
+ }
+
+ $operator = isset($matches[1]) ? $matches[1] : '==';
+ if ('since' === $operator || 'after' === $operator) {
+ $operator = '>';
+ }
+
+ if ('until' === $operator || 'before' === $operator) {
+ $operator = '<';
+ }
+
+ $this->setOperator($operator);
+ $this->setTarget($target);
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Comparator/NumberComparator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Comparator/NumberComparator.php
new file mode 100644
index 0000000..c8587dc
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Comparator/NumberComparator.php
@@ -0,0 +1,81 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Comparator;
+
+/**
+ * NumberComparator compiles a simple comparison to an anonymous
+ * subroutine, which you can call with a value to be tested again.
+ *
+ * Now this would be very pointless, if NumberCompare didn't understand
+ * magnitudes.
+ *
+ * The target value may use magnitudes of kilobytes (k, ki),
+ * megabytes (m, mi), or gigabytes (g, gi). Those suffixed
+ * with an i use the appropriate 2**n version in accordance with the
+ * IEC standard: http://physics.nist.gov/cuu/Units/binary.html
+ *
+ * Based on the Perl Number::Compare module.
+ *
+ * @author Fabien Potencier PHP port
+ * @author Richard Clamp Perl version
+ * @copyright 2004-2005 Fabien Potencier
+ * @copyright 2002 Richard Clamp
+ *
+ * @see http://physics.nist.gov/cuu/Units/binary.html
+ */
+class NumberComparator extends Comparator
+{
+ /**
+ * Constructor.
+ *
+ * @param string $test A comparison string
+ *
+ * @throws \InvalidArgumentException If the test is not understood
+ */
+ public function __construct($test)
+ {
+ if (!preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) {
+ throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test));
+ }
+
+ $target = $matches[2];
+ if (!is_numeric($target)) {
+ throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target));
+ }
+ if (isset($matches[3])) {
+ // magnitude
+ switch (strtolower($matches[3])) {
+ case 'k':
+ $target *= 1000;
+ break;
+ case 'ki':
+ $target *= 1024;
+ break;
+ case 'm':
+ $target *= 1000000;
+ break;
+ case 'mi':
+ $target *= 1024*1024;
+ break;
+ case 'g':
+ $target *= 1000000000;
+ break;
+ case 'gi':
+ $target *= 1024*1024*1024;
+ break;
+ }
+ }
+
+ $this->setTarget($target);
+ $this->setOperator(isset($matches[1]) ? $matches[1] : '==');
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Exception/AccessDeniedException.php b/console/skel/symfony/finder/Symfony/Component/Finder/Exception/AccessDeniedException.php
new file mode 100644
index 0000000..ee195ea
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Exception/AccessDeniedException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Exception;
+
+/**
+ * @author Jean-François Simon
+ */
+class AccessDeniedException extends \UnexpectedValueException
+{
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Exception/AdapterFailureException.php b/console/skel/symfony/finder/Symfony/Component/Finder/Exception/AdapterFailureException.php
new file mode 100644
index 0000000..15fa221
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Exception/AdapterFailureException.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Exception;
+
+use Symfony\Component\Finder\Adapter\AdapterInterface;
+
+/**
+ * Base exception for all adapter failures.
+ *
+ * @author Jean-François Simon
+ */
+class AdapterFailureException extends \RuntimeException implements ExceptionInterface
+{
+ /**
+ * @var \Symfony\Component\Finder\Adapter\AdapterInterface
+ */
+ private $adapter;
+
+ /**
+ * @param AdapterInterface $adapter
+ * @param string|null $message
+ * @param \Exception|null $previous
+ */
+ public function __construct(AdapterInterface $adapter, $message = null, \Exception $previous = null)
+ {
+ $this->adapter = $adapter;
+ parent::__construct($message ?: 'Search failed with "'.$adapter->getName().'" adapter.', $previous);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAdapter()
+ {
+ return $this->adapter;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Exception/ExceptionInterface.php b/console/skel/symfony/finder/Symfony/Component/Finder/Exception/ExceptionInterface.php
new file mode 100644
index 0000000..bff0214
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Exception/ExceptionInterface.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Exception;
+
+/**
+ * @author Jean-François Simon
+ */
+interface ExceptionInterface
+{
+ /**
+ * @return \Symfony\Component\Finder\Adapter\AdapterInterface
+ */
+ public function getAdapter();
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Exception/OperationNotPermitedException.php b/console/skel/symfony/finder/Symfony/Component/Finder/Exception/OperationNotPermitedException.php
new file mode 100644
index 0000000..3663112
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Exception/OperationNotPermitedException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Exception;
+
+/**
+ * @author Jean-François Simon
+ */
+class OperationNotPermitedException extends AdapterFailureException
+{
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Exception/ShellCommandFailureException.php b/console/skel/symfony/finder/Symfony/Component/Finder/Exception/ShellCommandFailureException.php
new file mode 100644
index 0000000..2658f6a
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Exception/ShellCommandFailureException.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Exception;
+
+use Symfony\Component\Finder\Adapter\AdapterInterface;
+use Symfony\Component\Finder\Shell\Command;
+
+/**
+ * @author Jean-François Simon
+ */
+class ShellCommandFailureException extends AdapterFailureException
+{
+ /**
+ * @var Command
+ */
+ private $command;
+
+ /**
+ * @param AdapterInterface $adapter
+ * @param Command $command
+ * @param \Exception|null $previous
+ */
+ public function __construct(AdapterInterface $adapter, Command $command, \Exception $previous = null)
+ {
+ $this->command = $command;
+ parent::__construct($adapter, 'Shell command failed: "'.$command->join().'".', $previous);
+ }
+
+ /**
+ * @return Command
+ */
+ public function getCommand()
+ {
+ return $this->command;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Expression/Expression.php b/console/skel/symfony/finder/Symfony/Component/Finder/Expression/Expression.php
new file mode 100644
index 0000000..9002607
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Expression/Expression.php
@@ -0,0 +1,146 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Expression;
+
+/**
+ * @author Jean-François Simon
+ */
+class Expression implements ValueInterface
+{
+ const TYPE_REGEX = 1;
+ const TYPE_GLOB = 2;
+
+ /**
+ * @var ValueInterface
+ */
+ private $value;
+
+ /**
+ * @param string $expr
+ *
+ * @return Expression
+ */
+ public static function create($expr)
+ {
+ return new self($expr);
+ }
+
+ /**
+ * @param string $expr
+ */
+ public function __construct($expr)
+ {
+ try {
+ $this->value = Regex::create($expr);
+ } catch (\InvalidArgumentException $e) {
+ $this->value = new Glob($expr);
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render()
+ {
+ return $this->value->render();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renderPattern()
+ {
+ return $this->value->renderPattern();
+ }
+
+ /**
+ * @return bool
+ */
+ public function isCaseSensitive()
+ {
+ return $this->value->isCaseSensitive();
+ }
+
+ /**
+ * @return int
+ */
+ public function getType()
+ {
+ return $this->value->getType();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prepend($expr)
+ {
+ $this->value->prepend($expr);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function append($expr)
+ {
+ $this->value->append($expr);
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isRegex()
+ {
+ return self::TYPE_REGEX === $this->value->getType();
+ }
+
+ /**
+ * @return bool
+ */
+ public function isGlob()
+ {
+ return self::TYPE_GLOB === $this->value->getType();
+ }
+
+ /**
+ * @throws \LogicException
+ *
+ * @return Glob
+ */
+ public function getGlob()
+ {
+ if (self::TYPE_GLOB !== $this->value->getType()) {
+ throw new \LogicException('Regex can\'t be transformed to glob.');
+ }
+
+ return $this->value;
+ }
+
+ /**
+ * @return Regex
+ */
+ public function getRegex()
+ {
+ return self::TYPE_REGEX === $this->value->getType() ? $this->value : $this->value->toRegex();
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Expression/Glob.php b/console/skel/symfony/finder/Symfony/Component/Finder/Expression/Glob.php
new file mode 100644
index 0000000..3023cee
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Expression/Glob.php
@@ -0,0 +1,157 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Expression;
+
+/**
+ * @author Jean-François Simon
+ */
+class Glob implements ValueInterface
+{
+ /**
+ * @var string
+ */
+ private $pattern;
+
+ /**
+ * @param string $pattern
+ */
+ public function __construct($pattern)
+ {
+ $this->pattern = $pattern;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render()
+ {
+ return $this->pattern;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renderPattern()
+ {
+ return $this->pattern;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getType()
+ {
+ return Expression::TYPE_GLOB;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isCaseSensitive()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prepend($expr)
+ {
+ $this->pattern = $expr.$this->pattern;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function append($expr)
+ {
+ $this->pattern .= $expr;
+
+ return $this;
+ }
+
+ /**
+ * Tests if glob is expandable ("*.{a,b}" syntax).
+ *
+ * @return bool
+ */
+ public function isExpandable()
+ {
+ return false !== strpos($this->pattern, '{')
+ && false !== strpos($this->pattern, '}');
+ }
+
+ /**
+ * @param bool $strictLeadingDot
+ * @param bool $strictWildcardSlash
+ *
+ * @return Regex
+ */
+ public function toRegex($strictLeadingDot = true, $strictWildcardSlash = true)
+ {
+ $firstByte = true;
+ $escaping = false;
+ $inCurlies = 0;
+ $regex = '';
+ $sizeGlob = strlen($this->pattern);
+ for ($i = 0; $i < $sizeGlob; $i++) {
+ $car = $this->pattern[$i];
+ if ($firstByte) {
+ if ($strictLeadingDot && '.' !== $car) {
+ $regex .= '(?=[^\.])';
+ }
+
+ $firstByte = false;
+ }
+
+ if ('/' === $car) {
+ $firstByte = true;
+ }
+
+ if ('.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) {
+ $regex .= "\\$car";
+ } elseif ('*' === $car) {
+ $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*');
+ } elseif ('?' === $car) {
+ $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.');
+ } elseif ('{' === $car) {
+ $regex .= $escaping ? '\\{' : '(';
+ if (!$escaping) {
+ ++$inCurlies;
+ }
+ } elseif ('}' === $car && $inCurlies) {
+ $regex .= $escaping ? '}' : ')';
+ if (!$escaping) {
+ --$inCurlies;
+ }
+ } elseif (',' === $car && $inCurlies) {
+ $regex .= $escaping ? ',' : '|';
+ } elseif ('\\' === $car) {
+ if ($escaping) {
+ $regex .= '\\\\';
+ $escaping = false;
+ } else {
+ $escaping = true;
+ }
+
+ continue;
+ } else {
+ $regex .= $car;
+ }
+ $escaping = false;
+ }
+
+ return new Regex('^'.$regex.'$');
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Expression/Regex.php b/console/skel/symfony/finder/Symfony/Component/Finder/Expression/Regex.php
new file mode 100644
index 0000000..a249fc2
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Expression/Regex.php
@@ -0,0 +1,321 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Expression;
+
+/**
+ * @author Jean-François Simon
+ */
+class Regex implements ValueInterface
+{
+ const START_FLAG = '^';
+ const END_FLAG = '$';
+ const BOUNDARY = '~';
+ const JOKER = '.*';
+ const ESCAPING = '\\';
+
+ /**
+ * @var string
+ */
+ private $pattern;
+
+ /**
+ * @var array
+ */
+ private $options;
+
+ /**
+ * @var bool
+ */
+ private $startFlag;
+
+ /**
+ * @var bool
+ */
+ private $endFlag;
+
+ /**
+ * @var bool
+ */
+ private $startJoker;
+
+ /**
+ * @var bool
+ */
+ private $endJoker;
+
+ /**
+ * @param string $expr
+ *
+ * @return Regex
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function create($expr)
+ {
+ if (preg_match('/^(.{3,}?)([imsxuADU]*)$/', $expr, $m)) {
+ $start = substr($m[1], 0, 1);
+ $end = substr($m[1], -1);
+
+ if (
+ ($start === $end && !preg_match('/[*?[:alnum:] \\\\]/', $start))
+ || ($start === '{' && $end === '}')
+ || ($start === '(' && $end === ')')
+ ) {
+ return new self(substr($m[1], 1, -1), $m[2], $end);
+ }
+ }
+
+ throw new \InvalidArgumentException('Given expression is not a regex.');
+ }
+
+ /**
+ * @param string $pattern
+ * @param string $options
+ * @param string $delimiter
+ */
+ public function __construct($pattern, $options = '', $delimiter = null)
+ {
+ if (null !== $delimiter) {
+ // removes delimiter escaping
+ $pattern = str_replace('\\'.$delimiter, $delimiter, $pattern);
+ }
+
+ $this->parsePattern($pattern);
+ $this->options = $options;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render()
+ {
+ return self::BOUNDARY
+ .$this->renderPattern()
+ .self::BOUNDARY
+ .$this->options;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renderPattern()
+ {
+ return ($this->startFlag ? self::START_FLAG : '')
+ .($this->startJoker ? self::JOKER : '')
+ .str_replace(self::BOUNDARY, '\\'.self::BOUNDARY, $this->pattern)
+ .($this->endJoker ? self::JOKER : '')
+ .($this->endFlag ? self::END_FLAG : '');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isCaseSensitive()
+ {
+ return !$this->hasOption('i');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getType()
+ {
+ return Expression::TYPE_REGEX;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prepend($expr)
+ {
+ $this->pattern = $expr.$this->pattern;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function append($expr)
+ {
+ $this->pattern .= $expr;
+
+ return $this;
+ }
+
+ /**
+ * @param string $option
+ *
+ * @return bool
+ */
+ public function hasOption($option)
+ {
+ return false !== strpos($this->options, $option);
+ }
+
+ /**
+ * @param string $option
+ *
+ * @return Regex
+ */
+ public function addOption($option)
+ {
+ if (!$this->hasOption($option)) {
+ $this->options .= $option;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $option
+ *
+ * @return Regex
+ */
+ public function removeOption($option)
+ {
+ $this->options = str_replace($option, '', $this->options);
+
+ return $this;
+ }
+
+ /**
+ * @param bool $startFlag
+ *
+ * @return Regex
+ */
+ public function setStartFlag($startFlag)
+ {
+ $this->startFlag = $startFlag;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasStartFlag()
+ {
+ return $this->startFlag;
+ }
+
+ /**
+ * @param bool $endFlag
+ *
+ * @return Regex
+ */
+ public function setEndFlag($endFlag)
+ {
+ $this->endFlag = (bool) $endFlag;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasEndFlag()
+ {
+ return $this->endFlag;
+ }
+
+ /**
+ * @param bool $startJoker
+ *
+ * @return Regex
+ */
+ public function setStartJoker($startJoker)
+ {
+ $this->startJoker = $startJoker;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasStartJoker()
+ {
+ return $this->startJoker;
+ }
+
+ /**
+ * @param bool $endJoker
+ *
+ * @return Regex
+ */
+ public function setEndJoker($endJoker)
+ {
+ $this->endJoker = (bool) $endJoker;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasEndJoker()
+ {
+ return $this->endJoker;
+ }
+
+ /**
+ * @param array $replacement
+ *
+ * @return Regex
+ */
+ public function replaceJokers($replacement)
+ {
+ $replace = function ($subject) use ($replacement) {
+ $subject = $subject[0];
+ $replace = 0 === substr_count($subject, '\\') % 2;
+
+ return $replace ? str_replace('.', $replacement, $subject) : $subject;
+ };
+
+ $this->pattern = preg_replace_callback('~[\\\\]*\\.~', $replace, $this->pattern);
+
+ return $this;
+ }
+
+ /**
+ * @param string $pattern
+ */
+ private function parsePattern($pattern)
+ {
+ if ($this->startFlag = self::START_FLAG === substr($pattern, 0, 1)) {
+ $pattern = substr($pattern, 1);
+ }
+
+ if ($this->startJoker = self::JOKER === substr($pattern, 0, 2)) {
+ $pattern = substr($pattern, 2);
+ }
+
+ if ($this->endFlag = (self::END_FLAG === substr($pattern, -1) && self::ESCAPING !== substr($pattern, -2, -1))) {
+ $pattern = substr($pattern, 0, -1);
+ }
+
+ if ($this->endJoker = (self::JOKER === substr($pattern, -2) && self::ESCAPING !== substr($pattern, -3, -2))) {
+ $pattern = substr($pattern, 0, -2);
+ }
+
+ $this->pattern = $pattern;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Expression/ValueInterface.php b/console/skel/symfony/finder/Symfony/Component/Finder/Expression/ValueInterface.php
new file mode 100644
index 0000000..34ce0e7
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Expression/ValueInterface.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Expression;
+
+/**
+ * @author Jean-François Simon
+ */
+interface ValueInterface
+{
+ /**
+ * Renders string representation of expression.
+ *
+ * @return string
+ */
+ public function render();
+
+ /**
+ * Renders string representation of pattern.
+ *
+ * @return string
+ */
+ public function renderPattern();
+
+ /**
+ * Returns value case sensitivity.
+ *
+ * @return bool
+ */
+ public function isCaseSensitive();
+
+ /**
+ * Returns expression type.
+ *
+ * @return int
+ */
+ public function getType();
+
+ /**
+ * @param string $expr
+ *
+ * @return ValueInterface
+ */
+ public function prepend($expr);
+
+ /**
+ * @param string $expr
+ *
+ * @return ValueInterface
+ */
+ public function append($expr);
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Finder.php b/console/skel/symfony/finder/Symfony/Component/Finder/Finder.php
new file mode 100644
index 0000000..91a2992
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Finder.php
@@ -0,0 +1,840 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder;
+
+use Symfony\Component\Finder\Adapter\AdapterInterface;
+use Symfony\Component\Finder\Adapter\GnuFindAdapter;
+use Symfony\Component\Finder\Adapter\BsdFindAdapter;
+use Symfony\Component\Finder\Adapter\PhpAdapter;
+use Symfony\Component\Finder\Comparator\DateComparator;
+use Symfony\Component\Finder\Comparator\NumberComparator;
+use Symfony\Component\Finder\Exception\ExceptionInterface;
+use Symfony\Component\Finder\Iterator\CustomFilterIterator;
+use Symfony\Component\Finder\Iterator\DateRangeFilterIterator;
+use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator;
+use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator;
+use Symfony\Component\Finder\Iterator\FilecontentFilterIterator;
+use Symfony\Component\Finder\Iterator\FilenameFilterIterator;
+use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator;
+use Symfony\Component\Finder\Iterator\SortableIterator;
+
+/**
+ * Finder allows to build rules to find files and directories.
+ *
+ * It is a thin wrapper around several specialized iterator classes.
+ *
+ * All rules may be invoked several times.
+ *
+ * All methods return the current Finder object to allow easy chaining:
+ *
+ * $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
+ *
+ * @author Fabien Potencier
+ *
+ * @api
+ */
+class Finder implements \IteratorAggregate, \Countable
+{
+ const IGNORE_VCS_FILES = 1;
+ const IGNORE_DOT_FILES = 2;
+
+ private $mode = 0;
+ private $names = array();
+ private $notNames = array();
+ private $exclude = array();
+ private $filters = array();
+ private $depths = array();
+ private $sizes = array();
+ private $followLinks = false;
+ private $sort = false;
+ private $ignore = 0;
+ private $dirs = array();
+ private $dates = array();
+ private $iterators = array();
+ private $contains = array();
+ private $notContains = array();
+ private $adapters = array();
+ private $paths = array();
+ private $notPaths = array();
+ private $ignoreUnreadableDirs = false;
+
+ private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg');
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
+
+ $this
+ ->addAdapter(new GnuFindAdapter())
+ ->addAdapter(new BsdFindAdapter())
+ ->addAdapter(new PhpAdapter(), -50)
+ ->setAdapter('php')
+ ;
+ }
+
+ /**
+ * Creates a new Finder.
+ *
+ * @return Finder A new Finder instance
+ *
+ * @api
+ */
+ public static function create()
+ {
+ return new static();
+ }
+
+ /**
+ * Registers a finder engine implementation.
+ *
+ * @param AdapterInterface $adapter An adapter instance
+ * @param int $priority Highest is selected first
+ *
+ * @return Finder The current Finder instance
+ */
+ public function addAdapter(AdapterInterface $adapter, $priority = 0)
+ {
+ $this->adapters[$adapter->getName()] = array(
+ 'adapter' => $adapter,
+ 'priority' => $priority,
+ 'selected' => false,
+ );
+
+ return $this->sortAdapters();
+ }
+
+ /**
+ * Sets the selected adapter to the best one according to the current platform the code is run on.
+ *
+ * @return Finder The current Finder instance
+ */
+ public function useBestAdapter()
+ {
+ $this->resetAdapterSelection();
+
+ return $this->sortAdapters();
+ }
+
+ /**
+ * Selects the adapter to use.
+ *
+ * @param string $name
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return Finder The current Finder instance
+ */
+ public function setAdapter($name)
+ {
+ if (!isset($this->adapters[$name])) {
+ throw new \InvalidArgumentException(sprintf('Adapter "%s" does not exist.', $name));
+ }
+
+ $this->resetAdapterSelection();
+ $this->adapters[$name]['selected'] = true;
+
+ return $this->sortAdapters();
+ }
+
+ /**
+ * Removes all adapters registered in the finder.
+ *
+ * @return Finder The current Finder instance
+ */
+ public function removeAdapters()
+ {
+ $this->adapters = array();
+
+ return $this;
+ }
+
+ /**
+ * Returns registered adapters ordered by priority without extra information.
+ *
+ * @return AdapterInterface[]
+ */
+ public function getAdapters()
+ {
+ return array_values(array_map(function (array $adapter) {
+ return $adapter['adapter'];
+ }, $this->adapters));
+ }
+
+ /**
+ * Restricts the matching to directories only.
+ *
+ * @return Finder The current Finder instance
+ *
+ * @api
+ */
+ public function directories()
+ {
+ $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;
+
+ return $this;
+ }
+
+ /**
+ * Restricts the matching to files only.
+ *
+ * @return Finder The current Finder instance
+ *
+ * @api
+ */
+ public function files()
+ {
+ $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;
+
+ return $this;
+ }
+
+ /**
+ * Adds tests for the directory depth.
+ *
+ * Usage:
+ *
+ * $finder->depth('> 1') // the Finder will start matching at level 1.
+ * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point.
+ *
+ * @param int $level The depth level expression
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see DepthRangeFilterIterator
+ * @see NumberComparator
+ *
+ * @api
+ */
+ public function depth($level)
+ {
+ $this->depths[] = new Comparator\NumberComparator($level);
+
+ return $this;
+ }
+
+ /**
+ * Adds tests for file dates (last modified).
+ *
+ * The date must be something that strtotime() is able to parse:
+ *
+ * $finder->date('since yesterday');
+ * $finder->date('until 2 days ago');
+ * $finder->date('> now - 2 hours');
+ * $finder->date('>= 2005-10-15');
+ *
+ * @param string $date A date rage string
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see strtotime
+ * @see DateRangeFilterIterator
+ * @see DateComparator
+ *
+ * @api
+ */
+ public function date($date)
+ {
+ $this->dates[] = new Comparator\DateComparator($date);
+
+ return $this;
+ }
+
+ /**
+ * Adds rules that files must match.
+ *
+ * You can use patterns (delimited with / sign), globs or simple strings.
+ *
+ * $finder->name('*.php')
+ * $finder->name('/\.php$/') // same as above
+ * $finder->name('test.php')
+ *
+ * @param string $pattern A pattern (a regexp, a glob, or a string)
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see FilenameFilterIterator
+ *
+ * @api
+ */
+ public function name($pattern)
+ {
+ $this->names[] = $pattern;
+
+ return $this;
+ }
+
+ /**
+ * Adds rules that files must not match.
+ *
+ * @param string $pattern A pattern (a regexp, a glob, or a string)
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see FilenameFilterIterator
+ *
+ * @api
+ */
+ public function notName($pattern)
+ {
+ $this->notNames[] = $pattern;
+
+ return $this;
+ }
+
+ /**
+ * Adds tests that file contents must match.
+ *
+ * Strings or PCRE patterns can be used:
+ *
+ * $finder->contains('Lorem ipsum')
+ * $finder->contains('/Lorem ipsum/i')
+ *
+ * @param string $pattern A pattern (string or regexp)
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see FilecontentFilterIterator
+ */
+ public function contains($pattern)
+ {
+ $this->contains[] = $pattern;
+
+ return $this;
+ }
+
+ /**
+ * Adds tests that file contents must not match.
+ *
+ * Strings or PCRE patterns can be used:
+ *
+ * $finder->notContains('Lorem ipsum')
+ * $finder->notContains('/Lorem ipsum/i')
+ *
+ * @param string $pattern A pattern (string or regexp)
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see FilecontentFilterIterator
+ */
+ public function notContains($pattern)
+ {
+ $this->notContains[] = $pattern;
+
+ return $this;
+ }
+
+ /**
+ * Adds rules that filenames must match.
+ *
+ * You can use patterns (delimited with / sign) or simple strings.
+ *
+ * $finder->path('some/special/dir')
+ * $finder->path('/some\/special\/dir/') // same as above
+ *
+ * Use only / as dirname separator.
+ *
+ * @param string $pattern A pattern (a regexp or a string)
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see FilenameFilterIterator
+ */
+ public function path($pattern)
+ {
+ $this->paths[] = $pattern;
+
+ return $this;
+ }
+
+ /**
+ * Adds rules that filenames must not match.
+ *
+ * You can use patterns (delimited with / sign) or simple strings.
+ *
+ * $finder->notPath('some/special/dir')
+ * $finder->notPath('/some\/special\/dir/') // same as above
+ *
+ * Use only / as dirname separator.
+ *
+ * @param string $pattern A pattern (a regexp or a string)
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see FilenameFilterIterator
+ */
+ public function notPath($pattern)
+ {
+ $this->notPaths[] = $pattern;
+
+ return $this;
+ }
+
+ /**
+ * Adds tests for file sizes.
+ *
+ * $finder->size('> 10K');
+ * $finder->size('<= 1Ki');
+ * $finder->size(4);
+ *
+ * @param string $size A size range string
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see SizeRangeFilterIterator
+ * @see NumberComparator
+ *
+ * @api
+ */
+ public function size($size)
+ {
+ $this->sizes[] = new Comparator\NumberComparator($size);
+
+ return $this;
+ }
+
+ /**
+ * Excludes directories.
+ *
+ * @param string|array $dirs A directory path or an array of directories
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see ExcludeDirectoryFilterIterator
+ *
+ * @api
+ */
+ public function exclude($dirs)
+ {
+ $this->exclude = array_merge($this->exclude, (array) $dirs);
+
+ return $this;
+ }
+
+ /**
+ * Excludes "hidden" directories and files (starting with a dot).
+ *
+ * @param bool $ignoreDotFiles Whether to exclude "hidden" files or not
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see ExcludeDirectoryFilterIterator
+ *
+ * @api
+ */
+ public function ignoreDotFiles($ignoreDotFiles)
+ {
+ if ($ignoreDotFiles) {
+ $this->ignore = $this->ignore | static::IGNORE_DOT_FILES;
+ } else {
+ $this->ignore = $this->ignore & ~static::IGNORE_DOT_FILES;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Forces the finder to ignore version control directories.
+ *
+ * @param bool $ignoreVCS Whether to exclude VCS files or not
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see ExcludeDirectoryFilterIterator
+ *
+ * @api
+ */
+ public function ignoreVCS($ignoreVCS)
+ {
+ if ($ignoreVCS) {
+ $this->ignore = $this->ignore | static::IGNORE_VCS_FILES;
+ } else {
+ $this->ignore = $this->ignore & ~static::IGNORE_VCS_FILES;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds VCS patterns.
+ *
+ * @see ignoreVCS()
+ *
+ * @param string|string[] $pattern VCS patterns to ignore
+ */
+ public static function addVCSPattern($pattern)
+ {
+ foreach ((array) $pattern as $p) {
+ self::$vcsPatterns[] = $p;
+ }
+
+ self::$vcsPatterns = array_unique(self::$vcsPatterns);
+ }
+
+ /**
+ * Sorts files and directories by an anonymous function.
+ *
+ * The anonymous function receives two \SplFileInfo instances to compare.
+ *
+ * This can be slow as all the matching files and directories must be retrieved for comparison.
+ *
+ * @param \Closure $closure An anonymous function
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see SortableIterator
+ *
+ * @api
+ */
+ public function sort(\Closure $closure)
+ {
+ $this->sort = $closure;
+
+ return $this;
+ }
+
+ /**
+ * Sorts files and directories by name.
+ *
+ * This can be slow as all the matching files and directories must be retrieved for comparison.
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see SortableIterator
+ *
+ * @api
+ */
+ public function sortByName()
+ {
+ $this->sort = Iterator\SortableIterator::SORT_BY_NAME;
+
+ return $this;
+ }
+
+ /**
+ * Sorts files and directories by type (directories before files), then by name.
+ *
+ * This can be slow as all the matching files and directories must be retrieved for comparison.
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see SortableIterator
+ *
+ * @api
+ */
+ public function sortByType()
+ {
+ $this->sort = Iterator\SortableIterator::SORT_BY_TYPE;
+
+ return $this;
+ }
+
+ /**
+ * Sorts files and directories by the last accessed time.
+ *
+ * This is the time that the file was last accessed, read or written to.
+ *
+ * This can be slow as all the matching files and directories must be retrieved for comparison.
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see SortableIterator
+ *
+ * @api
+ */
+ public function sortByAccessedTime()
+ {
+ $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME;
+
+ return $this;
+ }
+
+ /**
+ * Sorts files and directories by the last inode changed time.
+ *
+ * This is the time that the inode information was last modified (permissions, owner, group or other metadata).
+ *
+ * On Windows, since inode is not available, changed time is actually the file creation time.
+ *
+ * This can be slow as all the matching files and directories must be retrieved for comparison.
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see SortableIterator
+ *
+ * @api
+ */
+ public function sortByChangedTime()
+ {
+ $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME;
+
+ return $this;
+ }
+
+ /**
+ * Sorts files and directories by the last modified time.
+ *
+ * This is the last time the actual contents of the file were last modified.
+ *
+ * This can be slow as all the matching files and directories must be retrieved for comparison.
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see SortableIterator
+ *
+ * @api
+ */
+ public function sortByModifiedTime()
+ {
+ $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME;
+
+ return $this;
+ }
+
+ /**
+ * Filters the iterator with an anonymous function.
+ *
+ * The anonymous function receives a \SplFileInfo and must return false
+ * to remove files.
+ *
+ * @param \Closure $closure An anonymous function
+ *
+ * @return Finder The current Finder instance
+ *
+ * @see CustomFilterIterator
+ *
+ * @api
+ */
+ public function filter(\Closure $closure)
+ {
+ $this->filters[] = $closure;
+
+ return $this;
+ }
+
+ /**
+ * Forces the following of symlinks.
+ *
+ * @return Finder The current Finder instance
+ *
+ * @api
+ */
+ public function followLinks()
+ {
+ $this->followLinks = true;
+
+ return $this;
+ }
+
+ /**
+ * Tells finder to ignore unreadable directories.
+ *
+ * By default, scanning unreadable directories content throws an AccessDeniedException.
+ *
+ * @param bool $ignore
+ *
+ * @return Finder The current Finder instance
+ */
+ public function ignoreUnreadableDirs($ignore = true)
+ {
+ $this->ignoreUnreadableDirs = (bool) $ignore;
+
+ return $this;
+ }
+
+ /**
+ * Searches files and directories which match defined rules.
+ *
+ * @param string|array $dirs A directory path or an array of directories
+ *
+ * @return Finder The current Finder instance
+ *
+ * @throws \InvalidArgumentException if one of the directories does not exist
+ *
+ * @api
+ */
+ public function in($dirs)
+ {
+ $resolvedDirs = array();
+
+ foreach ((array) $dirs as $dir) {
+ if (is_dir($dir)) {
+ $resolvedDirs[] = $dir;
+ } elseif ($glob = glob($dir, GLOB_BRACE | GLOB_ONLYDIR)) {
+ $resolvedDirs = array_merge($resolvedDirs, $glob);
+ } else {
+ throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir));
+ }
+ }
+
+ $this->dirs = array_merge($this->dirs, $resolvedDirs);
+
+ return $this;
+ }
+
+ /**
+ * Returns an Iterator for the current Finder configuration.
+ *
+ * This method implements the IteratorAggregate interface.
+ *
+ * @return \Iterator An iterator
+ *
+ * @throws \LogicException if the in() method has not been called
+ */
+ public function getIterator()
+ {
+ if (0 === count($this->dirs) && 0 === count($this->iterators)) {
+ throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
+ }
+
+ if (1 === count($this->dirs) && 0 === count($this->iterators)) {
+ return $this->searchInDirectory($this->dirs[0]);
+ }
+
+ $iterator = new \AppendIterator();
+ foreach ($this->dirs as $dir) {
+ $iterator->append($this->searchInDirectory($dir));
+ }
+
+ foreach ($this->iterators as $it) {
+ $iterator->append($it);
+ }
+
+ return $iterator;
+ }
+
+ /**
+ * Appends an existing set of files/directories to the finder.
+ *
+ * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
+ *
+ * @param mixed $iterator
+ *
+ * @return Finder The finder
+ *
+ * @throws \InvalidArgumentException When the given argument is not iterable.
+ */
+ public function append($iterator)
+ {
+ if ($iterator instanceof \IteratorAggregate) {
+ $this->iterators[] = $iterator->getIterator();
+ } elseif ($iterator instanceof \Iterator) {
+ $this->iterators[] = $iterator;
+ } elseif ($iterator instanceof \Traversable || is_array($iterator)) {
+ $it = new \ArrayIterator();
+ foreach ($iterator as $file) {
+ $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file));
+ }
+ $this->iterators[] = $it;
+ } else {
+ throw new \InvalidArgumentException('Finder::append() method wrong argument type.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Counts all the results collected by the iterators.
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return iterator_count($this->getIterator());
+ }
+
+ /**
+ * @return Finder The current Finder instance
+ */
+ private function sortAdapters()
+ {
+ uasort($this->adapters, function (array $a, array $b) {
+ if ($a['selected'] || $b['selected']) {
+ return $a['selected'] ? -1 : 1;
+ }
+
+ return $a['priority'] > $b['priority'] ? -1 : 1;
+ });
+
+ return $this;
+ }
+
+ /**
+ * @param $dir
+ *
+ * @return \Iterator
+ *
+ * @throws \RuntimeException When none of the adapters are supported
+ */
+ private function searchInDirectory($dir)
+ {
+ if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
+ $this->exclude = array_merge($this->exclude, self::$vcsPatterns);
+ }
+
+ if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
+ $this->notPaths[] = '#(^|/)\..+(/|$)#';
+ }
+
+ foreach ($this->adapters as $adapter) {
+ if ($adapter['adapter']->isSupported()) {
+ try {
+ return $this
+ ->buildAdapter($adapter['adapter'])
+ ->searchInDirectory($dir);
+ } catch (ExceptionInterface $e) {
+ }
+ }
+ }
+
+ throw new \RuntimeException('No supported adapter found.');
+ }
+
+ /**
+ * @param AdapterInterface $adapter
+ *
+ * @return AdapterInterface
+ */
+ private function buildAdapter(AdapterInterface $adapter)
+ {
+ return $adapter
+ ->setFollowLinks($this->followLinks)
+ ->setDepths($this->depths)
+ ->setMode($this->mode)
+ ->setExclude($this->exclude)
+ ->setNames($this->names)
+ ->setNotNames($this->notNames)
+ ->setContains($this->contains)
+ ->setNotContains($this->notContains)
+ ->setSizes($this->sizes)
+ ->setDates($this->dates)
+ ->setFilters($this->filters)
+ ->setSort($this->sort)
+ ->setPath($this->paths)
+ ->setNotPath($this->notPaths)
+ ->ignoreUnreadableDirs($this->ignoreUnreadableDirs);
+ }
+
+ /**
+ * Unselects all adapters.
+ */
+ private function resetAdapterSelection()
+ {
+ $this->adapters = array_map(function (array $properties) {
+ $properties['selected'] = false;
+
+ return $properties;
+ }, $this->adapters);
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Glob.php b/console/skel/symfony/finder/Symfony/Component/Finder/Glob.php
new file mode 100644
index 0000000..c2030c9
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Glob.php
@@ -0,0 +1,103 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder;
+
+/**
+ * Glob matches globbing patterns against text.
+ *
+ * if match_glob("foo.*", "foo.bar") echo "matched\n";
+ *
+ * // prints foo.bar and foo.baz
+ * $regex = glob_to_regex("foo.*");
+ * for (array('foo.bar', 'foo.baz', 'foo', 'bar') as $t)
+ * {
+ * if (/$regex/) echo "matched: $car\n";
+ * }
+ *
+ * Glob implements glob(3) style matching that can be used to match
+ * against text, rather than fetching names from a filesystem.
+ *
+ * Based on the Perl Text::Glob module.
+ *
+ * @author Fabien Potencier PHP port
+ * @author Richard Clamp Perl version
+ * @copyright 2004-2005 Fabien Potencier
+ * @copyright 2002 Richard Clamp
+ */
+class Glob
+{
+ /**
+ * Returns a regexp which is the equivalent of the glob pattern.
+ *
+ * @param string $glob The glob pattern
+ * @param bool $strictLeadingDot
+ * @param bool $strictWildcardSlash
+ *
+ * @return string regex The regexp
+ */
+ public static function toRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true)
+ {
+ $firstByte = true;
+ $escaping = false;
+ $inCurlies = 0;
+ $regex = '';
+ $sizeGlob = strlen($glob);
+ for ($i = 0; $i < $sizeGlob; $i++) {
+ $car = $glob[$i];
+ if ($firstByte) {
+ if ($strictLeadingDot && '.' !== $car) {
+ $regex .= '(?=[^\.])';
+ }
+
+ $firstByte = false;
+ }
+
+ if ('/' === $car) {
+ $firstByte = true;
+ }
+
+ if ('.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) {
+ $regex .= "\\$car";
+ } elseif ('*' === $car) {
+ $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*');
+ } elseif ('?' === $car) {
+ $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.');
+ } elseif ('{' === $car) {
+ $regex .= $escaping ? '\\{' : '(';
+ if (!$escaping) {
+ ++$inCurlies;
+ }
+ } elseif ('}' === $car && $inCurlies) {
+ $regex .= $escaping ? '}' : ')';
+ if (!$escaping) {
+ --$inCurlies;
+ }
+ } elseif (',' === $car && $inCurlies) {
+ $regex .= $escaping ? ',' : '|';
+ } elseif ('\\' === $car) {
+ if ($escaping) {
+ $regex .= '\\\\';
+ $escaping = false;
+ } else {
+ $escaping = true;
+ }
+
+ continue;
+ } else {
+ $regex .= $car;
+ }
+ $escaping = false;
+ }
+
+ return '#^'.$regex.'$#';
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/CustomFilterIterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/CustomFilterIterator.php
new file mode 100644
index 0000000..24b15d9
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/CustomFilterIterator.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * CustomFilterIterator filters files by applying anonymous functions.
+ *
+ * The anonymous function receives a \SplFileInfo and must return false
+ * to remove files.
+ *
+ * @author Fabien Potencier
+ */
+class CustomFilterIterator extends FilterIterator
+{
+ private $filters = array();
+
+ /**
+ * Constructor.
+ *
+ * @param \Iterator $iterator The Iterator to filter
+ * @param array $filters An array of PHP callbacks
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function __construct(\Iterator $iterator, array $filters)
+ {
+ foreach ($filters as $filter) {
+ if (!is_callable($filter)) {
+ throw new \InvalidArgumentException('Invalid PHP callback.');
+ }
+ }
+ $this->filters = $filters;
+
+ parent::__construct($iterator);
+ }
+
+ /**
+ * Filters the iterator values.
+ *
+ * @return bool true if the value should be kept, false otherwise
+ */
+ public function accept()
+ {
+ $fileinfo = $this->current();
+
+ foreach ($this->filters as $filter) {
+ if (false === call_user_func($filter, $fileinfo)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/DateRangeFilterIterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/DateRangeFilterIterator.php
new file mode 100644
index 0000000..4d5ef9a
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/DateRangeFilterIterator.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+use Symfony\Component\Finder\Comparator\DateComparator;
+
+/**
+ * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates).
+ *
+ * @author Fabien Potencier
+ */
+class DateRangeFilterIterator extends FilterIterator
+{
+ private $comparators = array();
+
+ /**
+ * Constructor.
+ *
+ * @param \Iterator $iterator The Iterator to filter
+ * @param DateComparator[] $comparators An array of DateComparator instances
+ */
+ public function __construct(\Iterator $iterator, array $comparators)
+ {
+ $this->comparators = $comparators;
+
+ parent::__construct($iterator);
+ }
+
+ /**
+ * Filters the iterator values.
+ *
+ * @return bool true if the value should be kept, false otherwise
+ */
+ public function accept()
+ {
+ $fileinfo = $this->current();
+
+ if (!file_exists($fileinfo->getRealPath())) {
+ return false;
+ }
+
+ $filedate = $fileinfo->getMTime();
+ foreach ($this->comparators as $compare) {
+ if (!$compare->test($filedate)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php
new file mode 100644
index 0000000..f78c71e
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * DepthRangeFilterIterator limits the directory depth.
+ *
+ * @author Fabien Potencier
+ */
+class DepthRangeFilterIterator extends FilterIterator
+{
+ private $minDepth = 0;
+
+ /**
+ * Constructor.
+ *
+ * @param \RecursiveIteratorIterator $iterator The Iterator to filter
+ * @param int $minDepth The min depth
+ * @param int $maxDepth The max depth
+ */
+ public function __construct(\RecursiveIteratorIterator $iterator, $minDepth = 0, $maxDepth = PHP_INT_MAX)
+ {
+ $this->minDepth = $minDepth;
+ $iterator->setMaxDepth(PHP_INT_MAX === $maxDepth ? -1 : $maxDepth);
+
+ parent::__construct($iterator);
+ }
+
+ /**
+ * Filters the iterator values.
+ *
+ * @return bool true if the value should be kept, false otherwise
+ */
+ public function accept()
+ {
+ return $this->getInnerIterator()->getDepth() >= $this->minDepth;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php
new file mode 100644
index 0000000..1ddde85
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * ExcludeDirectoryFilterIterator filters out directories.
+ *
+ * @author Fabien Potencier
+ */
+class ExcludeDirectoryFilterIterator extends FilterIterator
+{
+ private $patterns = array();
+
+ /**
+ * Constructor.
+ *
+ * @param \Iterator $iterator The Iterator to filter
+ * @param array $directories An array of directories to exclude
+ */
+ public function __construct(\Iterator $iterator, array $directories)
+ {
+ foreach ($directories as $directory) {
+ $this->patterns[] = '#(^|/)'.preg_quote($directory, '#').'(/|$)#';
+ }
+
+ parent::__construct($iterator);
+ }
+
+ /**
+ * Filters the iterator values.
+ *
+ * @return bool true if the value should be kept, false otherwise
+ */
+ public function accept()
+ {
+ $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath();
+ $path = strtr($path, '\\', '/');
+ foreach ($this->patterns as $pattern) {
+ if (preg_match($pattern, $path)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/FilePathsIterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/FilePathsIterator.php
new file mode 100644
index 0000000..4da2f5b
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/FilePathsIterator.php
@@ -0,0 +1,131 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+use Symfony\Component\Finder\SplFileInfo;
+
+/**
+ * Iterate over shell command result.
+ *
+ * @author Jean-François Simon
+ */
+class FilePathsIterator extends \ArrayIterator
+{
+ /**
+ * @var string
+ */
+ private $baseDir;
+
+ /**
+ * @var int
+ */
+ private $baseDirLength;
+
+ /**
+ * @var string
+ */
+ private $subPath;
+
+ /**
+ * @var string
+ */
+ private $subPathname;
+
+ /**
+ * @var SplFileInfo
+ */
+ private $current;
+
+ /**
+ * @param array $paths List of paths returned by shell command
+ * @param string $baseDir Base dir for relative path building
+ */
+ public function __construct(array $paths, $baseDir)
+ {
+ $this->baseDir = $baseDir;
+ $this->baseDirLength = strlen($baseDir);
+
+ parent::__construct($paths);
+ }
+
+ /**
+ * @param string $name
+ * @param array $arguments
+ *
+ * @return mixed
+ */
+ public function __call($name, array $arguments)
+ {
+ return call_user_func_array(array($this->current(), $name), $arguments);
+ }
+
+ /**
+ * Return an instance of SplFileInfo with support for relative paths.
+ *
+ * @return SplFileInfo File information
+ */
+ public function current()
+ {
+ return $this->current;
+ }
+
+ /**
+ * @return string
+ */
+ public function key()
+ {
+ return $this->current->getPathname();
+ }
+
+ public function next()
+ {
+ parent::next();
+ $this->buildProperties();
+ }
+
+ public function rewind()
+ {
+ parent::rewind();
+ $this->buildProperties();
+ }
+
+ /**
+ * @return string
+ */
+ public function getSubPath()
+ {
+ return $this->subPath;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSubPathname()
+ {
+ return $this->subPathname;
+ }
+
+ private function buildProperties()
+ {
+ $absolutePath = parent::current();
+
+ if ($this->baseDir === substr($absolutePath, 0, $this->baseDirLength)) {
+ $this->subPathname = ltrim(substr($absolutePath, $this->baseDirLength), '/\\');
+ $dir = dirname($this->subPathname);
+ $this->subPath = '.' === $dir ? '' : $dir;
+ } else {
+ $this->subPath = $this->subPathname = '';
+ }
+
+ $this->current = new SplFileInfo(parent::current(), $this->subPath, $this->subPathname);
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/FileTypeFilterIterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/FileTypeFilterIterator.php
new file mode 100644
index 0000000..f50fd82
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/FileTypeFilterIterator.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * FileTypeFilterIterator only keeps files, directories, or both.
+ *
+ * @author Fabien Potencier
+ */
+class FileTypeFilterIterator extends FilterIterator
+{
+ const ONLY_FILES = 1;
+ const ONLY_DIRECTORIES = 2;
+
+ private $mode;
+
+ /**
+ * Constructor.
+ *
+ * @param \Iterator $iterator The Iterator to filter
+ * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES)
+ */
+ public function __construct(\Iterator $iterator, $mode)
+ {
+ $this->mode = $mode;
+
+ parent::__construct($iterator);
+ }
+
+ /**
+ * Filters the iterator values.
+ *
+ * @return bool true if the value should be kept, false otherwise
+ */
+ public function accept()
+ {
+ $fileinfo = $this->current();
+ if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) {
+ return false;
+ } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/FilecontentFilterIterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/FilecontentFilterIterator.php
new file mode 100644
index 0000000..28cf770
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/FilecontentFilterIterator.php
@@ -0,0 +1,76 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings).
+ *
+ * @author Fabien Potencier
+ * @author Włodzimierz Gajda
+ */
+class FilecontentFilterIterator extends MultiplePcreFilterIterator
+{
+ /**
+ * Filters the iterator values.
+ *
+ * @return bool true if the value should be kept, false otherwise
+ */
+ public function accept()
+ {
+ if (!$this->matchRegexps && !$this->noMatchRegexps) {
+ return true;
+ }
+
+ $fileinfo = $this->current();
+
+ if ($fileinfo->isDir() || !$fileinfo->isReadable()) {
+ return false;
+ }
+
+ $content = $fileinfo->getContents();
+ if (!$content) {
+ return false;
+ }
+
+ // should at least not match one rule to exclude
+ foreach ($this->noMatchRegexps as $regex) {
+ if (preg_match($regex, $content)) {
+ return false;
+ }
+ }
+
+ // should at least match one rule
+ $match = true;
+ if ($this->matchRegexps) {
+ $match = false;
+ foreach ($this->matchRegexps as $regex) {
+ if (preg_match($regex, $content)) {
+ return true;
+ }
+ }
+ }
+
+ return $match;
+ }
+
+ /**
+ * Converts string to regexp if necessary.
+ *
+ * @param string $str Pattern: string or regexp
+ *
+ * @return string regexp corresponding to a given string or regexp
+ */
+ protected function toRegex($str)
+ {
+ return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php
new file mode 100644
index 0000000..f1cd391
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+use Symfony\Component\Finder\Expression\Expression;
+
+/**
+ * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string).
+ *
+ * @author Fabien Potencier
+ */
+class FilenameFilterIterator extends MultiplePcreFilterIterator
+{
+ /**
+ * Filters the iterator values.
+ *
+ * @return bool true if the value should be kept, false otherwise
+ */
+ public function accept()
+ {
+ $filename = $this->current()->getFilename();
+
+ // should at least not match one rule to exclude
+ foreach ($this->noMatchRegexps as $regex) {
+ if (preg_match($regex, $filename)) {
+ return false;
+ }
+ }
+
+ // should at least match one rule
+ $match = true;
+ if ($this->matchRegexps) {
+ $match = false;
+ foreach ($this->matchRegexps as $regex) {
+ if (preg_match($regex, $filename)) {
+ return true;
+ }
+ }
+ }
+
+ return $match;
+ }
+
+ /**
+ * Converts glob to regexp.
+ *
+ * PCRE patterns are left unchanged.
+ * Glob strings are transformed with Glob::toRegex().
+ *
+ * @param string $str Pattern: glob or regexp
+ *
+ * @return string regexp corresponding to a given glob or regexp
+ */
+ protected function toRegex($str)
+ {
+ return Expression::create($str)->getRegex()->render();
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/FilterIterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/FilterIterator.php
new file mode 100644
index 0000000..f4da44c
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/FilterIterator.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * This iterator just overrides the rewind method in order to correct a PHP bug.
+ *
+ * @see https://bugs.php.net/bug.php?id=49104
+ *
+ * @author Alex Bogomazov
+ */
+abstract class FilterIterator extends \FilterIterator
+{
+ /**
+ * This is a workaround for the problem with \FilterIterator leaving inner \FilesystemIterator in wrong state after
+ * rewind in some cases.
+ *
+ * @see FilterIterator::rewind()
+ */
+ public function rewind()
+ {
+ $iterator = $this;
+ while ($iterator instanceof \OuterIterator) {
+ $innerIterator = $iterator->getInnerIterator();
+
+ if ($innerIterator instanceof RecursiveDirectoryIterator) {
+ if ($innerIterator->isRewindable()) {
+ $innerIterator->next();
+ $innerIterator->rewind();
+ }
+ } elseif ($iterator->getInnerIterator() instanceof \FilesystemIterator) {
+ $iterator->getInnerIterator()->next();
+ $iterator->getInnerIterator()->rewind();
+ }
+ $iterator = $iterator->getInnerIterator();
+ }
+
+ parent::rewind();
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php
new file mode 100644
index 0000000..068a7ef
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php
@@ -0,0 +1,66 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+use Symfony\Component\Finder\Expression\Expression;
+
+/**
+ * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings).
+ *
+ * @author Fabien Potencier
+ */
+abstract class MultiplePcreFilterIterator extends FilterIterator
+{
+ protected $matchRegexps = array();
+ protected $noMatchRegexps = array();
+
+ /**
+ * Constructor.
+ *
+ * @param \Iterator $iterator The Iterator to filter
+ * @param array $matchPatterns An array of patterns that need to match
+ * @param array $noMatchPatterns An array of patterns that need to not match
+ */
+ public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns)
+ {
+ foreach ($matchPatterns as $pattern) {
+ $this->matchRegexps[] = $this->toRegex($pattern);
+ }
+
+ foreach ($noMatchPatterns as $pattern) {
+ $this->noMatchRegexps[] = $this->toRegex($pattern);
+ }
+
+ parent::__construct($iterator);
+ }
+
+ /**
+ * Checks whether the string is a regex.
+ *
+ * @param string $str
+ *
+ * @return bool Whether the given string is a regex
+ */
+ protected function isRegex($str)
+ {
+ return Expression::create($str)->isRegex();
+ }
+
+ /**
+ * Converts string into regexp.
+ *
+ * @param string $str Pattern
+ *
+ * @return string regexp corresponding to a given string
+ */
+ abstract protected function toRegex($str);
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/PathFilterIterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/PathFilterIterator.php
new file mode 100644
index 0000000..2bb8ebd
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/PathFilterIterator.php
@@ -0,0 +1,74 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * PathFilterIterator filters files by path patterns (e.g. some/special/dir).
+ *
+ * @author Fabien Potencier
+ * @author Włodzimierz Gajda
+ */
+class PathFilterIterator extends MultiplePcreFilterIterator
+{
+ /**
+ * Filters the iterator values.
+ *
+ * @return bool true if the value should be kept, false otherwise
+ */
+ public function accept()
+ {
+ $filename = $this->current()->getRelativePathname();
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $filename = strtr($filename, '\\', '/');
+ }
+
+ // should at least not match one rule to exclude
+ foreach ($this->noMatchRegexps as $regex) {
+ if (preg_match($regex, $filename)) {
+ return false;
+ }
+ }
+
+ // should at least match one rule
+ $match = true;
+ if ($this->matchRegexps) {
+ $match = false;
+ foreach ($this->matchRegexps as $regex) {
+ if (preg_match($regex, $filename)) {
+ return true;
+ }
+ }
+ }
+
+ return $match;
+ }
+
+ /**
+ * Converts strings to regexp.
+ *
+ * PCRE patterns are left unchanged.
+ *
+ * Default conversion:
+ * 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/'
+ *
+ * Use only / as directory separator (on Windows also).
+ *
+ * @param string $str Pattern: regexp or dirname.
+ *
+ * @return string regexp corresponding to a given string or regexp
+ */
+ protected function toRegex($str)
+ {
+ return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php
new file mode 100644
index 0000000..af824d0
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php
@@ -0,0 +1,126 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+use Symfony\Component\Finder\Exception\AccessDeniedException;
+use Symfony\Component\Finder\SplFileInfo;
+
+/**
+ * Extends the \RecursiveDirectoryIterator to support relative paths.
+ *
+ * @author Victor Berchet
+ */
+class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
+{
+ /**
+ * @var bool
+ */
+ private $ignoreUnreadableDirs;
+
+ /**
+ * @var bool
+ */
+ private $rewindable;
+
+ /**
+ * Constructor.
+ *
+ * @param string $path
+ * @param int $flags
+ * @param bool $ignoreUnreadableDirs
+ *
+ * @throws \RuntimeException
+ */
+ public function __construct($path, $flags, $ignoreUnreadableDirs = false)
+ {
+ if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) {
+ throw new \RuntimeException('This iterator only support returning current as fileinfo.');
+ }
+
+ parent::__construct($path, $flags);
+ $this->ignoreUnreadableDirs = $ignoreUnreadableDirs;
+ }
+
+ /**
+ * Return an instance of SplFileInfo with support for relative paths.
+ *
+ * @return SplFileInfo File information
+ */
+ public function current()
+ {
+ return new SplFileInfo(parent::current()->getPathname(), $this->getSubPath(), $this->getSubPathname());
+ }
+
+ /**
+ * @return \RecursiveIterator
+ *
+ * @throws AccessDeniedException
+ */
+ public function getChildren()
+ {
+ try {
+ $children = parent::getChildren();
+
+ if ($children instanceof self) {
+ // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore
+ $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs;
+ }
+
+ return $children;
+ } catch (\UnexpectedValueException $e) {
+ if ($this->ignoreUnreadableDirs) {
+ // If directory is unreadable and finder is set to ignore it, a fake empty content is returned.
+ return new \RecursiveArrayIterator(array());
+ } else {
+ throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+ }
+
+ /**
+ * Do nothing for non rewindable stream.
+ */
+ public function rewind()
+ {
+ if (false === $this->isRewindable()) {
+ return;
+ }
+
+ // @see https://bugs.php.net/bug.php?id=49104
+ parent::next();
+
+ parent::rewind();
+ }
+
+ /**
+ * Checks if the stream is rewindable.
+ *
+ * @return bool true when the stream is rewindable, false otherwise
+ */
+ public function isRewindable()
+ {
+ if (null !== $this->rewindable) {
+ return $this->rewindable;
+ }
+
+ if (false !== $stream = @opendir($this->getPath())) {
+ $infos = stream_get_meta_data($stream);
+ closedir($stream);
+
+ if ($infos['seekable']) {
+ return $this->rewindable = true;
+ }
+ }
+
+ return $this->rewindable = false;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/SizeRangeFilterIterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/SizeRangeFilterIterator.php
new file mode 100644
index 0000000..3d3140a
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/SizeRangeFilterIterator.php
@@ -0,0 +1,59 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+use Symfony\Component\Finder\Comparator\NumberComparator;
+
+/**
+ * SizeRangeFilterIterator filters out files that are not in the given size range.
+ *
+ * @author Fabien Potencier
+ */
+class SizeRangeFilterIterator extends FilterIterator
+{
+ private $comparators = array();
+
+ /**
+ * Constructor.
+ *
+ * @param \Iterator $iterator The Iterator to filter
+ * @param NumberComparator[] $comparators An array of NumberComparator instances
+ */
+ public function __construct(\Iterator $iterator, array $comparators)
+ {
+ $this->comparators = $comparators;
+
+ parent::__construct($iterator);
+ }
+
+ /**
+ * Filters the iterator values.
+ *
+ * @return bool true if the value should be kept, false otherwise
+ */
+ public function accept()
+ {
+ $fileinfo = $this->current();
+ if (!$fileinfo->isFile()) {
+ return true;
+ }
+
+ $filesize = $fileinfo->getSize();
+ foreach ($this->comparators as $compare) {
+ if (!$compare->test($filesize)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/SortableIterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/SortableIterator.php
new file mode 100644
index 0000000..b32ac8d
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Iterator/SortableIterator.php
@@ -0,0 +1,82 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * SortableIterator applies a sort on a given Iterator.
+ *
+ * @author Fabien Potencier
+ */
+class SortableIterator implements \IteratorAggregate
+{
+ const SORT_BY_NAME = 1;
+ const SORT_BY_TYPE = 2;
+ const SORT_BY_ACCESSED_TIME = 3;
+ const SORT_BY_CHANGED_TIME = 4;
+ const SORT_BY_MODIFIED_TIME = 5;
+
+ private $iterator;
+ private $sort;
+
+ /**
+ * Constructor.
+ *
+ * @param \Traversable $iterator The Iterator to filter
+ * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback)
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function __construct(\Traversable $iterator, $sort)
+ {
+ $this->iterator = $iterator;
+
+ if (self::SORT_BY_NAME === $sort) {
+ $this->sort = function ($a, $b) {
+ return strcmp($a->getRealpath(), $b->getRealpath());
+ };
+ } elseif (self::SORT_BY_TYPE === $sort) {
+ $this->sort = function ($a, $b) {
+ if ($a->isDir() && $b->isFile()) {
+ return -1;
+ } elseif ($a->isFile() && $b->isDir()) {
+ return 1;
+ }
+
+ return strcmp($a->getRealpath(), $b->getRealpath());
+ };
+ } elseif (self::SORT_BY_ACCESSED_TIME === $sort) {
+ $this->sort = function ($a, $b) {
+ return ($a->getATime() - $b->getATime());
+ };
+ } elseif (self::SORT_BY_CHANGED_TIME === $sort) {
+ $this->sort = function ($a, $b) {
+ return ($a->getCTime() - $b->getCTime());
+ };
+ } elseif (self::SORT_BY_MODIFIED_TIME === $sort) {
+ $this->sort = function ($a, $b) {
+ return ($a->getMTime() - $b->getMTime());
+ };
+ } elseif (is_callable($sort)) {
+ $this->sort = $sort;
+ } else {
+ throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.');
+ }
+ }
+
+ public function getIterator()
+ {
+ $array = iterator_to_array($this->iterator, true);
+ uasort($array, $this->sort);
+
+ return new \ArrayIterator($array);
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/LICENSE b/console/skel/symfony/finder/Symfony/Component/Finder/LICENSE
new file mode 100644
index 0000000..43028bc
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2015 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/README.md b/console/skel/symfony/finder/Symfony/Component/Finder/README.md
new file mode 100644
index 0000000..7a96219
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/README.md
@@ -0,0 +1,53 @@
+Finder Component
+================
+
+Finder finds files and directories via an intuitive fluent interface.
+
+```php
+use Symfony\Component\Finder\Finder;
+
+$finder = new Finder();
+
+$iterator = $finder
+ ->files()
+ ->name('*.php')
+ ->depth(0)
+ ->size('>= 1K')
+ ->in(__DIR__);
+
+foreach ($iterator as $file) {
+ print $file->getRealpath()."\n";
+}
+```
+
+The iterator returns instances of [Symfony\Component\Finder\SplFileInfo\SplFileInfo][1].
+Besides the build-in methods inherited from [\SplFileInfo][2] (`getPerms()`, `getSize()`, ...),
+you can also use `getRelativePath()` and `getRelativePathname()`. Read the
+[official documentation][3] for more information.
+
+But you can also use it to find files stored remotely like in this example where
+we are looking for files on Amazon S3:
+
+```php
+$s3 = new \Zend_Service_Amazon_S3($key, $secret);
+$s3->registerStreamWrapper("s3");
+
+$finder = new Finder();
+$finder->name('photos*')->size('< 100K')->date('since 1 hour ago');
+foreach ($finder->in('s3://bucket-name') as $file) {
+ print $file->getFilename()."\n";
+}
+```
+
+Resources
+---------
+
+You can run the unit tests with the following command:
+
+ $ cd path/to/Symfony/Component/Finder/
+ $ composer.phar install
+ $ phpunit
+
+[1]: http://api.symfony.com/2.5/Symfony/Component/Finder/SplFileInfo.html
+[2]: http://php.net/splfileinfo
+[3]: http://symfony.com/doc/current/components/finder.html#usage
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Shell/Command.php b/console/skel/symfony/finder/Symfony/Component/Finder/Shell/Command.php
new file mode 100644
index 0000000..1f69afb
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Shell/Command.php
@@ -0,0 +1,294 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Shell;
+
+/**
+ * @author Jean-François Simon
+ */
+class Command
+{
+ /**
+ * @var Command|null
+ */
+ private $parent;
+
+ /**
+ * @var array
+ */
+ private $bits = array();
+
+ /**
+ * @var array
+ */
+ private $labels = array();
+
+ /**
+ * @var \Closure|null
+ */
+ private $errorHandler;
+
+ /**
+ * Constructor.
+ *
+ * @param Command|null $parent Parent command
+ */
+ public function __construct(Command $parent = null)
+ {
+ $this->parent = $parent;
+ }
+
+ /**
+ * Returns command as string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->join();
+ }
+
+ /**
+ * Creates a new Command instance.
+ *
+ * @param Command|null $parent Parent command
+ *
+ * @return Command New Command instance
+ */
+ public static function create(Command $parent = null)
+ {
+ return new self($parent);
+ }
+
+ /**
+ * Escapes special chars from input.
+ *
+ * @param string $input A string to escape
+ *
+ * @return string The escaped string
+ */
+ public static function escape($input)
+ {
+ return escapeshellcmd($input);
+ }
+
+ /**
+ * Quotes input.
+ *
+ * @param string $input An argument string
+ *
+ * @return string The quoted string
+ */
+ public static function quote($input)
+ {
+ return escapeshellarg($input);
+ }
+
+ /**
+ * Appends a string or a Command instance.
+ *
+ * @param string|Command $bit
+ *
+ * @return Command The current Command instance
+ */
+ public function add($bit)
+ {
+ $this->bits[] = $bit;
+
+ return $this;
+ }
+
+ /**
+ * Prepends a string or a command instance.
+ *
+ * @param string|Command $bit
+ *
+ * @return Command The current Command instance
+ */
+ public function top($bit)
+ {
+ array_unshift($this->bits, $bit);
+
+ foreach ($this->labels as $label => $index) {
+ $this->labels[$label] += 1;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Appends an argument, will be quoted.
+ *
+ * @param string $arg
+ *
+ * @return Command The current Command instance
+ */
+ public function arg($arg)
+ {
+ $this->bits[] = self::quote($arg);
+
+ return $this;
+ }
+
+ /**
+ * Appends escaped special command chars.
+ *
+ * @param string $esc
+ *
+ * @return Command The current Command instance
+ */
+ public function cmd($esc)
+ {
+ $this->bits[] = self::escape($esc);
+
+ return $this;
+ }
+
+ /**
+ * Inserts a labeled command to feed later.
+ *
+ * @param string $label The unique label
+ *
+ * @return Command The current Command instance
+ *
+ * @throws \RuntimeException If label already exists
+ */
+ public function ins($label)
+ {
+ if (isset($this->labels[$label])) {
+ throw new \RuntimeException(sprintf('Label "%s" already exists.', $label));
+ }
+
+ $this->bits[] = self::create($this);
+ $this->labels[$label] = count($this->bits)-1;
+
+ return $this->bits[$this->labels[$label]];
+ }
+
+ /**
+ * Retrieves a previously labeled command.
+ *
+ * @param string $label
+ *
+ * @return Command The labeled command
+ *
+ * @throws \RuntimeException
+ */
+ public function get($label)
+ {
+ if (!isset($this->labels[$label])) {
+ throw new \RuntimeException(sprintf('Label "%s" does not exist.', $label));
+ }
+
+ return $this->bits[$this->labels[$label]];
+ }
+
+ /**
+ * Returns parent command (if any).
+ *
+ * @return Command Parent command
+ *
+ * @throws \RuntimeException If command has no parent
+ */
+ public function end()
+ {
+ if (null === $this->parent) {
+ throw new \RuntimeException('Calling end on root command doesn\'t make sense.');
+ }
+
+ return $this->parent;
+ }
+
+ /**
+ * Counts bits stored in command.
+ *
+ * @return int The bits count
+ */
+ public function length()
+ {
+ return count($this->bits);
+ }
+
+ /**
+ * @param \Closure $errorHandler
+ *
+ * @return Command
+ */
+ public function setErrorHandler(\Closure $errorHandler)
+ {
+ $this->errorHandler = $errorHandler;
+
+ return $this;
+ }
+
+ /**
+ * @return \Closure|null
+ */
+ public function getErrorHandler()
+ {
+ return $this->errorHandler;
+ }
+
+ /**
+ * Executes current command.
+ *
+ * @return array The command result
+ *
+ * @throws \RuntimeException
+ */
+ public function execute()
+ {
+ if (null === $errorHandler = $this->errorHandler) {
+ exec($this->join(), $output);
+ } else {
+ $process = proc_open($this->join(), array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $pipes);
+ $output = preg_split('~(\r\n|\r|\n)~', stream_get_contents($pipes[1]), -1, PREG_SPLIT_NO_EMPTY);
+
+ if ($error = stream_get_contents($pipes[2])) {
+ $errorHandler($error);
+ }
+
+ proc_close($process);
+ }
+
+ return $output ?: array();
+ }
+
+ /**
+ * Joins bits.
+ *
+ * @return string
+ */
+ public function join()
+ {
+ return implode(' ', array_filter(
+ array_map(function ($bit) {
+ return $bit instanceof Command ? $bit->join() : ($bit ?: null);
+ }, $this->bits),
+ function ($bit) { return null !== $bit; }
+ ));
+ }
+
+ /**
+ * Insert a string or a Command instance before the bit at given position $index (index starts from 0).
+ *
+ * @param string|Command $bit
+ * @param int $index
+ *
+ * @return Command The current Command instance
+ */
+ public function addAtIndex($bit, $index)
+ {
+ array_splice($this->bits, $index, 0, $bit);
+
+ return $this;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Shell/Shell.php b/console/skel/symfony/finder/Symfony/Component/Finder/Shell/Shell.php
new file mode 100644
index 0000000..6d7bff3
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Shell/Shell.php
@@ -0,0 +1,97 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Shell;
+
+/**
+ * @author Jean-François Simon
+ */
+class Shell
+{
+ const TYPE_UNIX = 1;
+ const TYPE_DARWIN = 2;
+ const TYPE_CYGWIN = 3;
+ const TYPE_WINDOWS = 4;
+ const TYPE_BSD = 5;
+
+ /**
+ * @var string|null
+ */
+ private $type;
+
+ /**
+ * Returns guessed OS type.
+ *
+ * @return int
+ */
+ public function getType()
+ {
+ if (null === $this->type) {
+ $this->type = $this->guessType();
+ }
+
+ return $this->type;
+ }
+
+ /**
+ * Tests if a command is available.
+ *
+ * @param string $command
+ *
+ * @return bool
+ */
+ public function testCommand($command)
+ {
+ if (!function_exists('exec')) {
+ return false;
+ }
+
+ // todo: find a better way (command could not be available)
+ $testCommand = 'which ';
+ if (self::TYPE_WINDOWS === $this->type) {
+ $testCommand = 'where ';
+ }
+
+ $command = escapeshellcmd($command);
+
+ exec($testCommand.$command, $output, $code);
+
+ return 0 === $code && count($output) > 0;
+ }
+
+ /**
+ * Guesses OS type.
+ *
+ * @return int
+ */
+ private function guessType()
+ {
+ $os = strtolower(PHP_OS);
+
+ if (false !== strpos($os, 'cygwin')) {
+ return self::TYPE_CYGWIN;
+ }
+
+ if (false !== strpos($os, 'darwin')) {
+ return self::TYPE_DARWIN;
+ }
+
+ if (false !== strpos($os, 'bsd')) {
+ return self::TYPE_BSD;
+ }
+
+ if (0 === strpos($os, 'win')) {
+ return self::TYPE_WINDOWS;
+ }
+
+ return self::TYPE_UNIX;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/SplFileInfo.php b/console/skel/symfony/finder/Symfony/Component/Finder/SplFileInfo.php
new file mode 100644
index 0000000..c7fbe02
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/SplFileInfo.php
@@ -0,0 +1,77 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder;
+
+/**
+ * Extends \SplFileInfo to support relative paths.
+ *
+ * @author Fabien Potencier
+ */
+class SplFileInfo extends \SplFileInfo
+{
+ private $relativePath;
+ private $relativePathname;
+
+ /**
+ * Constructor.
+ *
+ * @param string $file The file name
+ * @param string $relativePath The relative path
+ * @param string $relativePathname The relative path name
+ */
+ public function __construct($file, $relativePath, $relativePathname)
+ {
+ parent::__construct($file);
+ $this->relativePath = $relativePath;
+ $this->relativePathname = $relativePathname;
+ }
+
+ /**
+ * Returns the relative path.
+ *
+ * @return string the relative path
+ */
+ public function getRelativePath()
+ {
+ return $this->relativePath;
+ }
+
+ /**
+ * Returns the relative path name.
+ *
+ * @return string the relative path name
+ */
+ public function getRelativePathname()
+ {
+ return $this->relativePathname;
+ }
+
+ /**
+ * Returns the contents of the file.
+ *
+ * @return string the contents of the file
+ *
+ * @throws \RuntimeException
+ */
+ public function getContents()
+ {
+ $level = error_reporting(0);
+ $content = file_get_contents($this->getPathname());
+ error_reporting($level);
+ if (false === $content) {
+ $error = error_get_last();
+ throw new \RuntimeException($error['message']);
+ }
+
+ return $content;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Comparator/ComparatorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Comparator/ComparatorTest.php
new file mode 100644
index 0000000..bf59844
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Comparator/ComparatorTest.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Comparator;
+
+use Symfony\Component\Finder\Comparator\Comparator;
+
+class ComparatorTest extends \PHPUnit_Framework_TestCase
+{
+ public function testGetSetOperator()
+ {
+ $comparator = new Comparator();
+ try {
+ $comparator->setOperator('foo');
+ $this->fail('->setOperator() throws an \InvalidArgumentException if the operator is not valid.');
+ } catch (\Exception $e) {
+ $this->assertInstanceOf('InvalidArgumentException', $e, '->setOperator() throws an \InvalidArgumentException if the operator is not valid.');
+ }
+
+ $comparator = new Comparator();
+ $comparator->setOperator('>');
+ $this->assertEquals('>', $comparator->getOperator(), '->getOperator() returns the current operator');
+ }
+
+ public function testGetSetTarget()
+ {
+ $comparator = new Comparator();
+ $comparator->setTarget(8);
+ $this->assertEquals(8, $comparator->getTarget(), '->getTarget() returns the target');
+ }
+
+ /**
+ * @dataProvider getTestData
+ */
+ public function testTest($operator, $target, $match, $noMatch)
+ {
+ $c = new Comparator();
+ $c->setOperator($operator);
+ $c->setTarget($target);
+
+ foreach ($match as $m) {
+ $this->assertTrue($c->test($m), '->test() tests a string against the expression');
+ }
+
+ foreach ($noMatch as $m) {
+ $this->assertFalse($c->test($m), '->test() tests a string against the expression');
+ }
+ }
+
+ public function getTestData()
+ {
+ return array(
+ array('<', '1000', array('500', '999'), array('1000', '1500')),
+ );
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Comparator/DateComparatorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Comparator/DateComparatorTest.php
new file mode 100644
index 0000000..2739126
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Comparator/DateComparatorTest.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Comparator;
+
+use Symfony\Component\Finder\Comparator\DateComparator;
+
+class DateComparatorTest extends \PHPUnit_Framework_TestCase
+{
+ public function testConstructor()
+ {
+ try {
+ new DateComparator('foobar');
+ $this->fail('__construct() throws an \InvalidArgumentException if the test expression is not valid.');
+ } catch (\Exception $e) {
+ $this->assertInstanceOf('InvalidArgumentException', $e, '__construct() throws an \InvalidArgumentException if the test expression is not valid.');
+ }
+
+ try {
+ new DateComparator('');
+ $this->fail('__construct() throws an \InvalidArgumentException if the test expression is not valid.');
+ } catch (\Exception $e) {
+ $this->assertInstanceOf('InvalidArgumentException', $e, '__construct() throws an \InvalidArgumentException if the test expression is not valid.');
+ }
+ }
+
+ /**
+ * @dataProvider getTestData
+ */
+ public function testTest($test, $match, $noMatch)
+ {
+ $c = new DateComparator($test);
+
+ foreach ($match as $m) {
+ $this->assertTrue($c->test($m), '->test() tests a string against the expression');
+ }
+
+ foreach ($noMatch as $m) {
+ $this->assertFalse($c->test($m), '->test() tests a string against the expression');
+ }
+ }
+
+ public function getTestData()
+ {
+ return array(
+ array('< 2005-10-10', array(strtotime('2005-10-09')), array(strtotime('2005-10-15'))),
+ array('until 2005-10-10', array(strtotime('2005-10-09')), array(strtotime('2005-10-15'))),
+ array('before 2005-10-10', array(strtotime('2005-10-09')), array(strtotime('2005-10-15'))),
+ array('> 2005-10-10', array(strtotime('2005-10-15')), array(strtotime('2005-10-09'))),
+ array('after 2005-10-10', array(strtotime('2005-10-15')), array(strtotime('2005-10-09'))),
+ array('since 2005-10-10', array(strtotime('2005-10-15')), array(strtotime('2005-10-09'))),
+ array('!= 2005-10-10', array(strtotime('2005-10-11')), array(strtotime('2005-10-10'))),
+ );
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Comparator/NumberComparatorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Comparator/NumberComparatorTest.php
new file mode 100644
index 0000000..d8cef1b
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Comparator/NumberComparatorTest.php
@@ -0,0 +1,107 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Comparator;
+
+use Symfony\Component\Finder\Comparator\NumberComparator;
+
+class NumberComparatorTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider getConstructorTestData
+ */
+ public function testConstructor($successes, $failures)
+ {
+ foreach ($successes as $s) {
+ new NumberComparator($s);
+ }
+
+ foreach ($failures as $f) {
+ try {
+ new NumberComparator($f);
+ $this->fail('__construct() throws an \InvalidArgumentException if the test expression is not valid.');
+ } catch (\Exception $e) {
+ $this->assertInstanceOf('InvalidArgumentException', $e, '__construct() throws an \InvalidArgumentException if the test expression is not valid.');
+ }
+ }
+ }
+
+ /**
+ * @dataProvider getTestData
+ */
+ public function testTest($test, $match, $noMatch)
+ {
+ $c = new NumberComparator($test);
+
+ foreach ($match as $m) {
+ $this->assertTrue($c->test($m), '->test() tests a string against the expression');
+ }
+
+ foreach ($noMatch as $m) {
+ $this->assertFalse($c->test($m), '->test() tests a string against the expression');
+ }
+ }
+
+ public function getTestData()
+ {
+ return array(
+ array('< 1000', array('500', '999'), array('1000', '1500')),
+
+ array('< 1K', array('500', '999'), array('1000', '1500')),
+ array('<1k', array('500', '999'), array('1000', '1500')),
+ array(' < 1 K ', array('500', '999'), array('1000', '1500')),
+ array('<= 1K', array('1000'), array('1001')),
+ array('> 1K', array('1001'), array('1000')),
+ array('>= 1K', array('1000'), array('999')),
+
+ array('< 1KI', array('500', '1023'), array('1024', '1500')),
+ array('<= 1KI', array('1024'), array('1025')),
+ array('> 1KI', array('1025'), array('1024')),
+ array('>= 1KI', array('1024'), array('1023')),
+
+ array('1KI', array('1024'), array('1023', '1025')),
+ array('==1KI', array('1024'), array('1023', '1025')),
+
+ array('==1m', array('1000000'), array('999999', '1000001')),
+ array('==1mi', array(1024*1024), array(1024*1024-1, 1024*1024+1)),
+
+ array('==1g', array('1000000000'), array('999999999', '1000000001')),
+ array('==1gi', array(1024*1024*1024), array(1024*1024*1024-1, 1024*1024*1024+1)),
+
+ array('!= 1000', array('500', '999'), array('1000')),
+ );
+ }
+
+ public function getConstructorTestData()
+ {
+ return array(
+ array(
+ array(
+ '1', '0',
+ '3.5', '33.55', '123.456', '123456.78',
+ '.1', '.123',
+ '.0', '0.0',
+ '1.', '0.', '123.',
+ '==1', '!=1', '<1', '>1', '<=1', '>=1',
+ '==1k', '==1ki', '==1m', '==1mi', '==1g', '==1gi',
+ '1k', '1ki', '1m', '1mi', '1g', '1gi',
+ ),
+ array(
+ false, null, '',
+ ' ', 'foobar',
+ '=1', '===1',
+ '0 . 1', '123 .45', '234. 567',
+ '..', '.0.', '0.1.2',
+ ),
+ ),
+ );
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Expression/ExpressionTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Expression/ExpressionTest.php
new file mode 100644
index 0000000..4254a45
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Expression/ExpressionTest.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Expression;
+
+use Symfony\Component\Finder\Expression\Expression;
+
+class ExpressionTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider getTypeGuesserData
+ */
+ public function testTypeGuesser($expr, $type)
+ {
+ $this->assertEquals($type, Expression::create($expr)->getType());
+ }
+
+ /**
+ * @dataProvider getCaseSensitiveData
+ */
+ public function testCaseSensitive($expr, $isCaseSensitive)
+ {
+ $this->assertEquals($isCaseSensitive, Expression::create($expr)->isCaseSensitive());
+ }
+
+ /**
+ * @dataProvider getRegexRenderingData
+ */
+ public function testRegexRendering($expr, $body)
+ {
+ $this->assertEquals($body, Expression::create($expr)->renderPattern());
+ }
+
+ public function getTypeGuesserData()
+ {
+ return array(
+ array('{foo}', Expression::TYPE_REGEX),
+ array('/foo/', Expression::TYPE_REGEX),
+ array('foo', Expression::TYPE_GLOB),
+ array('foo*', Expression::TYPE_GLOB),
+ );
+ }
+
+ public function getCaseSensitiveData()
+ {
+ return array(
+ array('{foo}m', true),
+ array('/foo/i', false),
+ array('foo*', true),
+ );
+ }
+
+ public function getRegexRenderingData()
+ {
+ return array(
+ array('{foo}m', 'foo'),
+ array('/foo/i', 'foo'),
+ );
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Expression/GlobTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Expression/GlobTest.php
new file mode 100644
index 0000000..9d4c3e5
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Expression/GlobTest.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Expression;
+
+use Symfony\Component\Finder\Expression\Expression;
+
+class GlobTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider getToRegexData
+ */
+ public function testGlobToRegex($glob, $match, $noMatch)
+ {
+ foreach ($match as $m) {
+ $this->assertRegExp(Expression::create($glob)->getRegex()->render(), $m, '::toRegex() converts a glob to a regexp');
+ }
+
+ foreach ($noMatch as $m) {
+ $this->assertNotRegExp(Expression::create($glob)->getRegex()->render(), $m, '::toRegex() converts a glob to a regexp');
+ }
+ }
+
+ public function getToRegexData()
+ {
+ return array(
+ array('', array(''), array('f', '/')),
+ array('*', array('foo'), array('foo/', '/foo')),
+ array('foo.*', array('foo.php', 'foo.a', 'foo.'), array('fooo.php', 'foo.php/foo')),
+ array('fo?', array('foo', 'fot'), array('fooo', 'ffoo', 'fo/')),
+ array('fo{o,t}', array('foo', 'fot'), array('fob', 'fo/')),
+ array('foo(bar|foo)', array('foo(bar|foo)'), array('foobar', 'foofoo')),
+ array('foo,bar', array('foo,bar'), array('foo', 'bar')),
+ array('fo{o,\\,}', array('foo', 'fo,'), array()),
+ array('fo{o,\\\\}', array('foo', 'fo\\'), array()),
+ array('/foo', array('/foo'), array('foo')),
+ );
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Expression/RegexTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Expression/RegexTest.php
new file mode 100644
index 0000000..620ba10
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Expression/RegexTest.php
@@ -0,0 +1,143 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Expression;
+
+use Symfony\Component\Finder\Expression\Expression;
+
+class RegexTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider getHasFlagsData
+ */
+ public function testHasFlags($regex, $start, $end)
+ {
+ $expr = new Expression($regex);
+
+ $this->assertEquals($start, $expr->getRegex()->hasStartFlag());
+ $this->assertEquals($end, $expr->getRegex()->hasEndFlag());
+ }
+
+ /**
+ * @dataProvider getHasJokersData
+ */
+ public function testHasJokers($regex, $start, $end)
+ {
+ $expr = new Expression($regex);
+
+ $this->assertEquals($start, $expr->getRegex()->hasStartJoker());
+ $this->assertEquals($end, $expr->getRegex()->hasEndJoker());
+ }
+
+ /**
+ * @dataProvider getSetFlagsData
+ */
+ public function testSetFlags($regex, $start, $end, $expected)
+ {
+ $expr = new Expression($regex);
+ $expr->getRegex()->setStartFlag($start)->setEndFlag($end);
+
+ $this->assertEquals($expected, $expr->render());
+ }
+
+ /**
+ * @dataProvider getSetJokersData
+ */
+ public function testSetJokers($regex, $start, $end, $expected)
+ {
+ $expr = new Expression($regex);
+ $expr->getRegex()->setStartJoker($start)->setEndJoker($end);
+
+ $this->assertEquals($expected, $expr->render());
+ }
+
+ public function testOptions()
+ {
+ $expr = new Expression('~abc~is');
+ $expr->getRegex()->removeOption('i')->addOption('m');
+
+ $this->assertEquals('~abc~sm', $expr->render());
+ }
+
+ public function testMixFlagsAndJokers()
+ {
+ $expr = new Expression('~^.*abc.*$~is');
+
+ $expr->getRegex()->setStartFlag(false)->setEndFlag(false)->setStartJoker(false)->setEndJoker(false);
+ $this->assertEquals('~abc~is', $expr->render());
+
+ $expr->getRegex()->setStartFlag(true)->setEndFlag(true)->setStartJoker(true)->setEndJoker(true);
+ $this->assertEquals('~^.*abc.*$~is', $expr->render());
+ }
+
+ /**
+ * @dataProvider getReplaceJokersTestData
+ */
+ public function testReplaceJokers($regex, $expected)
+ {
+ $expr = new Expression($regex);
+ $expr = $expr->getRegex()->replaceJokers('@');
+
+ $this->assertEquals($expected, $expr->renderPattern());
+ }
+
+ public function getHasFlagsData()
+ {
+ return array(
+ array('~^abc~', true, false),
+ array('~abc$~', false, true),
+ array('~abc~', false, false),
+ array('~^abc$~', true, true),
+ array('~^abc\\$~', true, false),
+ );
+ }
+
+ public function getHasJokersData()
+ {
+ return array(
+ array('~.*abc~', true, false),
+ array('~abc.*~', false, true),
+ array('~abc~', false, false),
+ array('~.*abc.*~', true, true),
+ array('~.*abc\\.*~', true, false),
+ );
+ }
+
+ public function getSetFlagsData()
+ {
+ return array(
+ array('~abc~', true, false, '~^abc~'),
+ array('~abc~', false, true, '~abc$~'),
+ array('~abc~', false, false, '~abc~'),
+ array('~abc~', true, true, '~^abc$~'),
+ );
+ }
+
+ public function getSetJokersData()
+ {
+ return array(
+ array('~abc~', true, false, '~.*abc~'),
+ array('~abc~', false, true, '~abc.*~'),
+ array('~abc~', false, false, '~abc~'),
+ array('~abc~', true, true, '~.*abc.*~'),
+ );
+ }
+
+ public function getReplaceJokersTestData()
+ {
+ return array(
+ array('~.abc~', '@abc'),
+ array('~\\.abc~', '\\.abc'),
+ array('~\\\\.abc~', '\\\\@abc'),
+ array('~\\\\\\.abc~', '\\\\\\.abc'),
+ );
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php
new file mode 100644
index 0000000..0cbae14
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\FakeAdapter;
+
+use Symfony\Component\Finder\Adapter\AbstractAdapter;
+
+/**
+ * @author Jean-François Simon
+ */
+class DummyAdapter extends AbstractAdapter
+{
+ /**
+ * @var \Iterator
+ */
+ private $iterator;
+
+ /**
+ * @param \Iterator $iterator
+ */
+ public function __construct(\Iterator $iterator)
+ {
+ $this->iterator = $iterator;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function searchInDirectory($dir)
+ {
+ return $this->iterator;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'yes';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function canBeUsed()
+ {
+ return true;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.php
new file mode 100644
index 0000000..6e6ed24
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\FakeAdapter;
+
+use Symfony\Component\Finder\Adapter\AbstractAdapter;
+use Symfony\Component\Finder\Exception\AdapterFailureException;
+
+/**
+ * @author Jean-François Simon
+ */
+class FailingAdapter extends AbstractAdapter
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function searchInDirectory($dir)
+ {
+ throw new AdapterFailureException($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'failing';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function canBeUsed()
+ {
+ return true;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php
new file mode 100644
index 0000000..5a260b0
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\FakeAdapter;
+
+use Symfony\Component\Finder\Adapter\AbstractAdapter;
+
+/**
+ * @author Jean-François Simon
+ */
+class NamedAdapter extends AbstractAdapter
+{
+ /**
+ * @var string
+ */
+ private $name;
+
+ /**
+ * @param string $name
+ */
+ public function __construct($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function searchInDirectory($dir)
+ {
+ return new \ArrayIterator(array());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function canBeUsed()
+ {
+ return true;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php
new file mode 100644
index 0000000..1f91b98
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\FakeAdapter;
+
+use Symfony\Component\Finder\Adapter\AbstractAdapter;
+
+/**
+ * @author Jean-François Simon
+ */
+class UnsupportedAdapter extends AbstractAdapter
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function searchInDirectory($dir)
+ {
+ return new \ArrayIterator(array());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'unsupported';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function canBeUsed()
+ {
+ return false;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/FinderTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/FinderTest.php
new file mode 100644
index 0000000..12f0678
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/FinderTest.php
@@ -0,0 +1,869 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests;
+
+use Symfony\Component\Finder\Finder;
+use Symfony\Component\Finder\Adapter;
+
+class FinderTest extends Iterator\RealIteratorTestCase
+{
+ public function testCreate()
+ {
+ $this->assertInstanceOf('Symfony\Component\Finder\Finder', Finder::create());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testDirectories($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->directories());
+ $this->assertIterator($this->toAbsolute(array('foo', 'toto')), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $finder->directories();
+ $finder->files();
+ $finder->directories();
+ $this->assertIterator($this->toAbsolute(array('foo', 'toto')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testFiles($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->files());
+ $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'test.py', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $finder->files();
+ $finder->directories();
+ $finder->files();
+ $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'test.py', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testDepth($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->depth('< 1'));
+ $this->assertIterator($this->toAbsolute(array('foo', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->depth('<= 0'));
+ $this->assertIterator($this->toAbsolute(array('foo', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->depth('>= 1'));
+ $this->assertIterator($this->toAbsolute(array('foo/bar.tmp')), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $finder->depth('< 1')->depth('>= 1');
+ $this->assertIterator(array(), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testName($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->name('*.php'));
+ $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $finder->name('test.ph*');
+ $finder->name('test.py');
+ $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $finder->name('~^test~i');
+ $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $finder->name('~\\.php$~i');
+ $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $finder->name('test.p{hp,y}');
+ $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testNotName($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->notName('*.php'));
+ $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $finder->notName('*.php');
+ $finder->notName('*.py');
+ $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $finder->name('test.ph*');
+ $finder->name('test.py');
+ $finder->notName('*.php');
+ $finder->notName('*.py');
+ $this->assertIterator(array(), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $finder->name('test.ph*');
+ $finder->name('test.py');
+ $finder->notName('*.p{hp,y}');
+ $this->assertIterator(array(), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getRegexNameTestData
+ *
+ * @group regexName
+ */
+ public function testRegexName($adapter, $regex)
+ {
+ $finder = $this->buildFinder($adapter);
+ $finder->name($regex);
+ $this->assertIterator($this->toAbsolute(array('test.py', 'test.php')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testSize($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->files()->size('< 1K')->size('> 500'));
+ $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testDate($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->files()->date('until last month'));
+ $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testExclude($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->exclude('foo'));
+ $this->assertIterator($this->toAbsolute(array('test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testIgnoreVCS($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->ignoreVCS(false)->ignoreDotFiles(false));
+ $this->assertIterator($this->toAbsolute(array('.git', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $finder->ignoreVCS(false)->ignoreVCS(false)->ignoreDotFiles(false);
+ $this->assertIterator($this->toAbsolute(array('.git', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->ignoreVCS(true)->ignoreDotFiles(false));
+ $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testIgnoreDotFiles($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->ignoreDotFiles(false)->ignoreVCS(false));
+ $this->assertIterator($this->toAbsolute(array('.git', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $finder->ignoreDotFiles(false)->ignoreDotFiles(false)->ignoreVCS(false);
+ $this->assertIterator($this->toAbsolute(array('.git', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->ignoreDotFiles(true)->ignoreVCS(false));
+ $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testSortByName($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->sortByName());
+ $this->assertIterator($this->toAbsolute(array('foo', 'foo bar', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testSortByType($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->sortByType());
+ $this->assertIterator($this->toAbsolute(array('foo', 'foo bar', 'toto', 'foo/bar.tmp', 'test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testSortByAccessedTime($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->sortByAccessedTime());
+ $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'toto', 'test.py', 'foo', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testSortByChangedTime($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->sortByChangedTime());
+ $this->assertIterator($this->toAbsolute(array('toto', 'test.py', 'test.php', 'foo/bar.tmp', 'foo', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testSortByModifiedTime($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->sortByModifiedTime());
+ $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'toto', 'test.py', 'foo', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testSort($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->sort(function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealpath(), $b->getRealpath()); }));
+ $this->assertIterator($this->toAbsolute(array('foo', 'foo bar', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testFilter($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->filter(function (\SplFileInfo $f) { return preg_match('/test/', $f) > 0; }));
+ $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testFollowLinks($adapter)
+ {
+ if ('\\' == DIRECTORY_SEPARATOR) {
+ return;
+ }
+
+ $finder = $this->buildFinder($adapter);
+ $this->assertSame($finder, $finder->followLinks());
+ $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testIn($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ try {
+ $finder->in('foobar');
+ $this->fail('->in() throws a \InvalidArgumentException if the directory does not exist');
+ } catch (\Exception $e) {
+ $this->assertInstanceOf('InvalidArgumentException', $e, '->in() throws a \InvalidArgumentException if the directory does not exist');
+ }
+
+ $finder = $this->buildFinder($adapter);
+ $iterator = $finder->files()->name('*.php')->depth('< 1')->in(array(self::$tmpDir, __DIR__))->getIterator();
+
+ $this->assertIterator(array(self::$tmpDir.DIRECTORY_SEPARATOR.'test.php', __DIR__.DIRECTORY_SEPARATOR.'FinderTest.php'), $iterator);
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testInWithGlob($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $finder->in(array(__DIR__.'/Fixtures/*/B/C', __DIR__.'/Fixtures/*/*/B/C'))->getIterator();
+
+ $this->assertIterator($this->toAbsoluteFixtures(array('A/B/C/abc.dat', 'copy/A/B/C/abc.dat.copy')), $finder);
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ * @expectedException \InvalidArgumentException
+ */
+ public function testInWithNonDirectoryGlob($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $finder->in(__DIR__.'/Fixtures/A/a*');
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testInWithGlobBrace($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $finder->in(array(__DIR__.'/Fixtures/{A,copy/A}/B/C'))->getIterator();
+
+ $this->assertIterator($this->toAbsoluteFixtures(array('A/B/C/abc.dat', 'copy/A/B/C/abc.dat.copy')), $finder);
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testGetIterator($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ try {
+ $finder->getIterator();
+ $this->fail('->getIterator() throws a \LogicException if the in() method has not been called');
+ } catch (\Exception $e) {
+ $this->assertInstanceOf('LogicException', $e, '->getIterator() throws a \LogicException if the in() method has not been called');
+ }
+
+ $finder = $this->buildFinder($adapter);
+ $dirs = array();
+ foreach ($finder->directories()->in(self::$tmpDir) as $dir) {
+ $dirs[] = (string) $dir;
+ }
+
+ $expected = $this->toAbsolute(array('foo', 'toto'));
+
+ sort($dirs);
+ sort($expected);
+
+ $this->assertEquals($expected, $dirs, 'implements the \IteratorAggregate interface');
+
+ $finder = $this->buildFinder($adapter);
+ $this->assertEquals(2, iterator_count($finder->directories()->in(self::$tmpDir)), 'implements the \IteratorAggregate interface');
+
+ $finder = $this->buildFinder($adapter);
+ $a = iterator_to_array($finder->directories()->in(self::$tmpDir));
+ $a = array_values(array_map(function ($a) { return (string) $a; }, $a));
+ sort($a);
+ $this->assertEquals($expected, $a, 'implements the \IteratorAggregate interface');
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testRelativePath($adapter)
+ {
+ $finder = $this->buildFinder($adapter)->in(self::$tmpDir);
+
+ $paths = array();
+
+ foreach ($finder as $file) {
+ $paths[] = $file->getRelativePath();
+ }
+
+ $ref = array("", "", "", "", "foo", "");
+
+ sort($ref);
+ sort($paths);
+
+ $this->assertEquals($ref, $paths);
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testRelativePathname($adapter)
+ {
+ $finder = $this->buildFinder($adapter)->in(self::$tmpDir)->sortByName();
+
+ $paths = array();
+
+ foreach ($finder as $file) {
+ $paths[] = $file->getRelativePathname();
+ }
+
+ $ref = array("test.php", "toto", "test.py", "foo", "foo".DIRECTORY_SEPARATOR."bar.tmp", "foo bar");
+
+ sort($paths);
+ sort($ref);
+
+ $this->assertEquals($ref, $paths);
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testAppendWithAFinder($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $finder->files()->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo');
+
+ $finder1 = $this->buildFinder($adapter);
+ $finder1->directories()->in(self::$tmpDir);
+
+ $finder = $finder->append($finder1);
+
+ $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto')), $finder->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testAppendWithAnArray($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $finder->files()->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo');
+
+ $finder->append($this->toAbsolute(array('foo', 'toto')));
+
+ $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto')), $finder->getIterator());
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testAppendReturnsAFinder($adapter)
+ {
+ $this->assertInstanceOf('Symfony\\Component\\Finder\\Finder', $this->buildFinder($adapter)->append(array()));
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testAppendDoesNotRequireIn($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $finder->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo');
+
+ $finder1 = Finder::create()->append($finder);
+
+ $this->assertIterator(iterator_to_array($finder->getIterator()), $finder1->getIterator());
+ }
+
+ public function testCountDirectories()
+ {
+ $directory = Finder::create()->directories()->in(self::$tmpDir);
+ $i = 0;
+
+ foreach ($directory as $dir) {
+ $i++;
+ }
+
+ $this->assertCount($i, $directory);
+ }
+
+ public function testCountFiles()
+ {
+ $files = Finder::create()->files()->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $i = 0;
+
+ foreach ($files as $file) {
+ $i++;
+ }
+
+ $this->assertCount($i, $files);
+ }
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testCountWithoutIn()
+ {
+ $finder = Finder::create()->files();
+ count($finder);
+ }
+
+ /**
+ * @dataProvider getContainsTestData
+ * @group grep
+ */
+ public function testContains($adapter, $matchPatterns, $noMatchPatterns, $expected)
+ {
+ $finder = $this->buildFinder($adapter);
+ $finder->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures')
+ ->name('*.txt')->sortByName()
+ ->contains($matchPatterns)
+ ->notContains($noMatchPatterns);
+
+ $this->assertIterator($this->toAbsoluteFixtures($expected), $finder);
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testContainsOnDirectory(Adapter\AdapterInterface $adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $finder->in(__DIR__)
+ ->directories()
+ ->name('Fixtures')
+ ->contains('abc');
+ $this->assertIterator(array(), $finder);
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testNotContainsOnDirectory(Adapter\AdapterInterface $adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $finder->in(__DIR__)
+ ->directories()
+ ->name('Fixtures')
+ ->notContains('abc');
+ $this->assertIterator(array(), $finder);
+ }
+
+ /**
+ * Searching in multiple locations involves AppendIterator which does an unnecessary rewind which leaves FilterIterator
+ * with inner FilesystemIterator in an invalid state.
+ *
+ * @see https://bugs.php.net/bug.php?id=49104
+ *
+ * @dataProvider getAdaptersTestData
+ */
+ public function testMultipleLocations(Adapter\AdapterInterface $adapter)
+ {
+ $locations = array(
+ self::$tmpDir.'/',
+ self::$tmpDir.'/toto/',
+ );
+
+ // it is expected that there are test.py test.php in the tmpDir
+ $finder = $this->buildFinder($adapter);
+ $finder->in($locations)->depth('< 1')->name('test.php');
+
+ $this->assertCount(1, $finder);
+ }
+
+ /**
+ * Iterator keys must be the file pathname.
+ *
+ * @dataProvider getAdaptersTestData
+ */
+ public function testIteratorKeys(Adapter\AdapterInterface $adapter)
+ {
+ $finder = $this->buildFinder($adapter)->in(self::$tmpDir);
+ foreach ($finder as $key => $file) {
+ $this->assertEquals($file->getPathname(), $key);
+ }
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testRegexSpecialCharsLocationWithPathRestrictionContainingStartFlag(Adapter\AdapterInterface $adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $finder->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'r+e.gex[c]a(r)s')
+ ->path('/^dir/');
+
+ $expected = array('r+e.gex[c]a(r)s'.DIRECTORY_SEPARATOR.'dir',
+ 'r+e.gex[c]a(r)s'.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'bar.dat',);
+ $this->assertIterator($this->toAbsoluteFixtures($expected), $finder);
+ }
+
+ public function testAdaptersOrdering()
+ {
+ $finder = Finder::create()
+ ->removeAdapters()
+ ->addAdapter(new FakeAdapter\NamedAdapter('a'), 0)
+ ->addAdapter(new FakeAdapter\NamedAdapter('b'), -50)
+ ->addAdapter(new FakeAdapter\NamedAdapter('c'), 50)
+ ->addAdapter(new FakeAdapter\NamedAdapter('d'), -25)
+ ->addAdapter(new FakeAdapter\NamedAdapter('e'), 25);
+
+ $this->assertEquals(
+ array('c', 'e', 'a', 'd', 'b'),
+ array_map(function (Adapter\AdapterInterface $adapter) {
+ return $adapter->getName();
+ }, $finder->getAdapters())
+ );
+ }
+
+ public function testAdaptersChaining()
+ {
+ $iterator = new \ArrayIterator(array());
+ $filenames = $this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto'));
+ foreach ($filenames as $file) {
+ $iterator->append(new \Symfony\Component\Finder\SplFileInfo($file, null, null));
+ }
+
+ $finder = Finder::create()
+ ->removeAdapters()
+ ->addAdapter(new FakeAdapter\UnsupportedAdapter(), 3)
+ ->addAdapter(new FakeAdapter\FailingAdapter(), 2)
+ ->addAdapter(new FakeAdapter\DummyAdapter($iterator), 1);
+
+ $this->assertIterator($filenames, $finder->in(sys_get_temp_dir())->getIterator());
+ }
+
+ public function getAdaptersTestData()
+ {
+ return array_map(
+ function ($adapter) { return array($adapter); },
+ $this->getValidAdapters()
+ );
+ }
+
+ public function getContainsTestData()
+ {
+ $tests = array(
+ array('', '', array()),
+ array('foo', 'bar', array()),
+ array('', 'foobar', array('dolor.txt', 'ipsum.txt', 'lorem.txt')),
+ array('lorem ipsum dolor sit amet', 'foobar', array('lorem.txt')),
+ array('sit', 'bar', array('dolor.txt', 'ipsum.txt', 'lorem.txt')),
+ array('dolor sit amet', '@^L@m', array('dolor.txt', 'ipsum.txt')),
+ array('/^lorem ipsum dolor sit amet$/m', 'foobar', array('lorem.txt')),
+ array('lorem', 'foobar', array('lorem.txt')),
+ array('', 'lorem', array('dolor.txt', 'ipsum.txt')),
+ array('ipsum dolor sit amet', '/^IPSUM/m', array('lorem.txt')),
+ );
+
+ return $this->buildTestData($tests);
+ }
+
+ public function getRegexNameTestData()
+ {
+ $tests = array(
+ array('~.+\\.p.+~i'),
+ array('~t.*s~i'),
+ );
+
+ return $this->buildTestData($tests);
+ }
+
+ /**
+ * @dataProvider getTestPathData
+ */
+ public function testPath(Adapter\AdapterInterface $adapter, $matchPatterns, $noMatchPatterns, array $expected)
+ {
+ $finder = $this->buildFinder($adapter);
+ $finder->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures')
+ ->path($matchPatterns)
+ ->notPath($noMatchPatterns);
+
+ $this->assertIterator($this->toAbsoluteFixtures($expected), $finder);
+ }
+
+ public function testAdapterSelection()
+ {
+ // test that by default, PhpAdapter is selected
+ $adapters = Finder::create()->getAdapters();
+ $this->assertTrue($adapters[0] instanceof Adapter\PhpAdapter);
+
+ // test another adapter selection
+ $adapters = Finder::create()->setAdapter('gnu_find')->getAdapters();
+ $this->assertTrue($adapters[0] instanceof Adapter\GnuFindAdapter);
+
+ // test that useBestAdapter method removes selection
+ $adapters = Finder::create()->useBestAdapter()->getAdapters();
+ $this->assertFalse($adapters[0] instanceof Adapter\PhpAdapter);
+ }
+
+ public function getTestPathData()
+ {
+ $tests = array(
+ array('', '', array()),
+ array('/^A\/B\/C/', '/C$/',
+ array('A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat'),
+ ),
+ array('/^A\/B/', 'foobar',
+ array(
+ 'A'.DIRECTORY_SEPARATOR.'B',
+ 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C',
+ 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat',
+ 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat',
+ ),
+ ),
+ array('A/B/C', 'foobar',
+ array(
+ 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C',
+ 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat',
+ 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C',
+ 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat.copy',
+ ),
+ ),
+ array('A/B', 'foobar',
+ array(
+ //dirs
+ 'A'.DIRECTORY_SEPARATOR.'B',
+ 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C',
+ 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B',
+ 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C',
+ //files
+ 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat',
+ 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat',
+ 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat.copy',
+ 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat.copy',
+ ),
+ ),
+ array('/^with space\//', 'foobar',
+ array(
+ 'with space'.DIRECTORY_SEPARATOR.'foo.txt',
+ ),
+ ),
+ );
+
+ return $this->buildTestData($tests);
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testAccessDeniedException(Adapter\AdapterInterface $adapter)
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('chmod is not supported on Windows');
+ }
+
+ $finder = $this->buildFinder($adapter);
+ $finder->files()->in(self::$tmpDir);
+
+ // make 'foo' directory non-readable
+ $testDir = self::$tmpDir.DIRECTORY_SEPARATOR.'foo';
+ chmod($testDir, 0333);
+
+ if (false === $couldRead = is_readable($testDir)) {
+ try {
+ $this->assertIterator($this->toAbsolute(array('foo bar', 'test.php', 'test.py')), $finder->getIterator());
+ $this->fail('Finder should throw an exception when opening a non-readable directory.');
+ } catch (\Exception $e) {
+ $expectedExceptionClass = 'Symfony\\Component\\Finder\\Exception\\AccessDeniedException';
+ if ($e instanceof \PHPUnit_Framework_ExpectationFailedException) {
+ $this->fail(sprintf("Expected exception:\n%s\nGot:\n%s\nWith comparison failure:\n%s", $expectedExceptionClass, 'PHPUnit_Framework_ExpectationFailedException', $e->getComparisonFailure()->getExpectedAsString()));
+ }
+
+ $this->assertInstanceOf($expectedExceptionClass, $e);
+ }
+ }
+
+ // restore original permissions
+ chmod($testDir, 0777);
+ clearstatcache($testDir);
+
+ if ($couldRead) {
+ $this->markTestSkipped('could read test files while test requires unreadable');
+ }
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testIgnoredAccessDeniedException(Adapter\AdapterInterface $adapter)
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('chmod is not supported on Windows');
+ }
+
+ $finder = $this->buildFinder($adapter);
+ $finder->files()->ignoreUnreadableDirs()->in(self::$tmpDir);
+
+ // make 'foo' directory non-readable
+ $testDir = self::$tmpDir.DIRECTORY_SEPARATOR.'foo';
+ chmod($testDir, 0333);
+
+ if (false === ($couldRead = is_readable($testDir))) {
+ $this->assertIterator($this->toAbsolute(array('foo bar', 'test.php', 'test.py')), $finder->getIterator());
+ }
+
+ // restore original permissions
+ chmod($testDir, 0777);
+ clearstatcache($testDir);
+
+ if ($couldRead) {
+ $this->markTestSkipped('could read test files while test requires unreadable');
+ }
+ }
+
+ private function buildTestData(array $tests)
+ {
+ $data = array();
+ foreach ($this->getValidAdapters() as $adapter) {
+ foreach ($tests as $test) {
+ $data[] = array_merge(array($adapter), $test);
+ }
+ }
+
+ return $data;
+ }
+
+ private function buildFinder(Adapter\AdapterInterface $adapter)
+ {
+ return Finder::create()
+ ->removeAdapters()
+ ->addAdapter($adapter);
+ }
+
+ private function getValidAdapters()
+ {
+ return array_filter(
+ array(
+ new Adapter\BsdFindAdapter(),
+ new Adapter\GnuFindAdapter(),
+ new Adapter\PhpAdapter(),
+ ),
+ function (Adapter\AdapterInterface $adapter) {
+ return $adapter->isSupported();
+ }
+ );
+ }
+
+ /**
+ * Searching in multiple locations with sub directories involves
+ * AppendIterator which does an unnecessary rewind which leaves
+ * FilterIterator with inner FilesystemIterator in an invalid state.
+ *
+ * @see https://bugs.php.net/bug.php?id=49104
+ */
+ public function testMultipleLocationsWithSubDirectories()
+ {
+ $locations = array(
+ __DIR__.'/Fixtures/one',
+ self::$tmpDir.DIRECTORY_SEPARATOR.'toto',
+ );
+
+ $finder = new Finder();
+ $finder->in($locations)->depth('< 10')->name('*.neon');
+
+ $expected = array(
+ __DIR__.'/Fixtures/one'.DIRECTORY_SEPARATOR.'b'.DIRECTORY_SEPARATOR.'c.neon',
+ __DIR__.'/Fixtures/one'.DIRECTORY_SEPARATOR.'b'.DIRECTORY_SEPARATOR.'d.neon',
+ );
+
+ $this->assertIterator($expected, $finder);
+ $this->assertIteratorInForeach($expected, $finder);
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/A/B/C/abc.dat b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/A/B/C/abc.dat
new file mode 100644
index 0000000..e69de29
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/A/B/ab.dat b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/A/B/ab.dat
new file mode 100644
index 0000000..e69de29
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/A/a.dat b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/A/a.dat
new file mode 100644
index 0000000..e69de29
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/copy/A/B/C/abc.dat.copy b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/copy/A/B/C/abc.dat.copy
new file mode 100644
index 0000000..e69de29
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/copy/A/B/ab.dat.copy b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/copy/A/B/ab.dat.copy
new file mode 100644
index 0000000..e69de29
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/copy/A/a.dat.copy b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/copy/A/a.dat.copy
new file mode 100644
index 0000000..e69de29
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/dolor.txt b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/dolor.txt
new file mode 100644
index 0000000..658bec6
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/dolor.txt
@@ -0,0 +1,2 @@
+dolor sit amet
+DOLOR SIT AMET
\ No newline at end of file
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/ipsum.txt b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/ipsum.txt
new file mode 100644
index 0000000..c7f392d
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/ipsum.txt
@@ -0,0 +1,2 @@
+ipsum dolor sit amet
+IPSUM DOLOR SIT AMET
\ No newline at end of file
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/lorem.txt b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/lorem.txt
new file mode 100644
index 0000000..2991a2c
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/lorem.txt
@@ -0,0 +1,2 @@
+lorem ipsum dolor sit amet
+LOREM IPSUM DOLOR SIT AMET
\ No newline at end of file
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/one/a b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/one/a
new file mode 100644
index 0000000..e69de29
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/one/b/c.neon b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/one/b/c.neon
new file mode 100644
index 0000000..e69de29
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/one/b/d.neon b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/one/b/d.neon
new file mode 100644
index 0000000..e69de29
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/r+e.gex[c]a(r)s/dir/bar.dat b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/r+e.gex[c]a(r)s/dir/bar.dat
new file mode 100644
index 0000000..e69de29
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/with space/foo.txt b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/with space/foo.txt
new file mode 100644
index 0000000..e69de29
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/CustomFilterIteratorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/CustomFilterIteratorTest.php
new file mode 100644
index 0000000..62629b1
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/CustomFilterIteratorTest.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+use Symfony\Component\Finder\Iterator\CustomFilterIterator;
+
+class CustomFilterIteratorTest extends IteratorTestCase
+{
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testWithInvalidFilter()
+ {
+ new CustomFilterIterator(new Iterator(), array('foo'));
+ }
+
+ /**
+ * @dataProvider getAcceptData
+ */
+ public function testAccept($filters, $expected)
+ {
+ $inner = new Iterator(array('test.php', 'test.py', 'foo.php'));
+
+ $iterator = new CustomFilterIterator($inner, $filters);
+
+ $this->assertIterator($expected, $iterator);
+ }
+
+ public function getAcceptData()
+ {
+ return array(
+ array(array(function (\SplFileInfo $fileinfo) { return false; }), array()),
+ array(array(function (\SplFileInfo $fileinfo) { return preg_match('/^test/', $fileinfo) > 0; }), array('test.php', 'test.py')),
+ array(array('is_dir'), array()),
+ );
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/DateRangeFilterIteratorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/DateRangeFilterIteratorTest.php
new file mode 100644
index 0000000..709d5fe
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/DateRangeFilterIteratorTest.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+use Symfony\Component\Finder\Iterator\DateRangeFilterIterator;
+use Symfony\Component\Finder\Comparator\DateComparator;
+
+class DateRangeFilterIteratorTest extends RealIteratorTestCase
+{
+ /**
+ * @dataProvider getAcceptData
+ */
+ public function testAccept($size, $expected)
+ {
+ $files = self::$files;
+ $files[] = self::toAbsolute('doesnotexist');
+ $inner = new Iterator($files);
+
+ $iterator = new DateRangeFilterIterator($inner, $size);
+
+ $this->assertIterator($expected, $iterator);
+ }
+
+ public function getAcceptData()
+ {
+ $since20YearsAgo = array(
+ '.git',
+ 'test.py',
+ 'foo',
+ 'foo/bar.tmp',
+ 'test.php',
+ 'toto',
+ '.bar',
+ '.foo',
+ '.foo/.bar',
+ 'foo bar',
+ '.foo/bar',
+ );
+
+ $since2MonthsAgo = array(
+ '.git',
+ 'test.py',
+ 'foo',
+ 'toto',
+ '.bar',
+ '.foo',
+ '.foo/.bar',
+ 'foo bar',
+ '.foo/bar',
+ );
+
+ $untilLastMonth = array(
+ 'foo/bar.tmp',
+ 'test.php',
+ );
+
+ return array(
+ array(array(new DateComparator('since 20 years ago')), $this->toAbsolute($since20YearsAgo)),
+ array(array(new DateComparator('since 2 months ago')), $this->toAbsolute($since2MonthsAgo)),
+ array(array(new DateComparator('until last month')), $this->toAbsolute($untilLastMonth)),
+ );
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/DepthRangeFilterIteratorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/DepthRangeFilterIteratorTest.php
new file mode 100644
index 0000000..5ec9832
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/DepthRangeFilterIteratorTest.php
@@ -0,0 +1,80 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator;
+
+class DepthRangeFilterIteratorTest extends RealIteratorTestCase
+{
+ /**
+ * @dataProvider getAcceptData
+ */
+ public function testAccept($minDepth, $maxDepth, $expected)
+ {
+ $inner = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->toAbsolute(), \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
+
+ $iterator = new DepthRangeFilterIterator($inner, $minDepth, $maxDepth);
+
+ $actual = array_keys(iterator_to_array($iterator));
+ sort($expected);
+ sort($actual);
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function getAcceptData()
+ {
+ $lessThan1 = array(
+ '.git',
+ 'test.py',
+ 'foo',
+ 'test.php',
+ 'toto',
+ '.foo',
+ '.bar',
+ 'foo bar',
+ );
+
+ $lessThanOrEqualTo1 = array(
+ '.git',
+ 'test.py',
+ 'foo',
+ 'foo/bar.tmp',
+ 'test.php',
+ 'toto',
+ '.foo',
+ '.foo/.bar',
+ '.bar',
+ 'foo bar',
+ '.foo/bar',
+ );
+
+ $graterThanOrEqualTo1 = array(
+ 'foo/bar.tmp',
+ '.foo/.bar',
+ '.foo/bar',
+ );
+
+ $equalTo1 = array(
+ 'foo/bar.tmp',
+ '.foo/.bar',
+ '.foo/bar',
+ );
+
+ return array(
+ array(0, 0, $this->toAbsolute($lessThan1)),
+ array(0, 1, $this->toAbsolute($lessThanOrEqualTo1)),
+ array(2, PHP_INT_MAX, array()),
+ array(1, PHP_INT_MAX, $this->toAbsolute($graterThanOrEqualTo1)),
+ array(1, 1, $this->toAbsolute($equalTo1)),
+ );
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/ExcludeDirectoryFilterIteratorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/ExcludeDirectoryFilterIteratorTest.php
new file mode 100644
index 0000000..693b733
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/ExcludeDirectoryFilterIteratorTest.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator;
+use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator;
+
+class ExcludeDirectoryFilterIteratorTest extends RealIteratorTestCase
+{
+ /**
+ * @dataProvider getAcceptData
+ */
+ public function testAccept($directories, $expected)
+ {
+ $inner = new \RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->toAbsolute(), \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
+
+ $iterator = new ExcludeDirectoryFilterIterator($inner, $directories);
+
+ $this->assertIterator($expected, $iterator);
+ }
+
+ public function getAcceptData()
+ {
+ $foo = array(
+ '.bar',
+ '.foo',
+ '.foo/.bar',
+ '.foo/bar',
+ '.git',
+ 'test.py',
+ 'test.php',
+ 'toto',
+ 'foo bar',
+ );
+
+ $fo = array(
+ '.bar',
+ '.foo',
+ '.foo/.bar',
+ '.foo/bar',
+ '.git',
+ 'test.py',
+ 'foo',
+ 'foo/bar.tmp',
+ 'test.php',
+ 'toto',
+ 'foo bar',
+ );
+
+ return array(
+ array(array('foo'), $this->toAbsolute($foo)),
+ array(array('fo'), $this->toAbsolute($fo)),
+ );
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilePathsIteratorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilePathsIteratorTest.php
new file mode 100644
index 0000000..89853a8
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilePathsIteratorTest.php
@@ -0,0 +1,66 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+use Symfony\Component\Finder\Iterator\FilePathsIterator;
+
+class FilePathsIteratorTest extends RealIteratorTestCase
+{
+ /**
+ * @dataProvider getSubPathData
+ */
+ public function testSubPath($baseDir, array $paths, array $subPaths, array $subPathnames)
+ {
+ $iterator = new FilePathsIterator($paths, $baseDir);
+
+ foreach ($iterator as $index => $file) {
+ $this->assertEquals($paths[$index], $file->getPathname());
+ $this->assertEquals($subPaths[$index], $iterator->getSubPath());
+ $this->assertEquals($subPathnames[$index], $iterator->getSubPathname());
+ }
+ }
+
+ public function getSubPathData()
+ {
+ $tmpDir = sys_get_temp_dir().'/symfony_finder';
+
+ return array(
+ array(
+ $tmpDir,
+ array( // paths
+ $tmpDir.DIRECTORY_SEPARATOR.'.git' => $tmpDir.DIRECTORY_SEPARATOR.'.git',
+ $tmpDir.DIRECTORY_SEPARATOR.'test.py' => $tmpDir.DIRECTORY_SEPARATOR.'test.py',
+ $tmpDir.DIRECTORY_SEPARATOR.'foo' => $tmpDir.DIRECTORY_SEPARATOR.'foo',
+ $tmpDir.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'bar.tmp' => $tmpDir.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'bar.tmp',
+ $tmpDir.DIRECTORY_SEPARATOR.'test.php' => $tmpDir.DIRECTORY_SEPARATOR.'test.php',
+ $tmpDir.DIRECTORY_SEPARATOR.'toto' => $tmpDir.DIRECTORY_SEPARATOR.'toto',
+ ),
+ array( // subPaths
+ $tmpDir.DIRECTORY_SEPARATOR.'.git' => '',
+ $tmpDir.DIRECTORY_SEPARATOR.'test.py' => '',
+ $tmpDir.DIRECTORY_SEPARATOR.'foo' => '',
+ $tmpDir.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'bar.tmp' => 'foo',
+ $tmpDir.DIRECTORY_SEPARATOR.'test.php' => '',
+ $tmpDir.DIRECTORY_SEPARATOR.'toto' => '',
+ ),
+ array( // subPathnames
+ $tmpDir.DIRECTORY_SEPARATOR.'.git' => '.git',
+ $tmpDir.DIRECTORY_SEPARATOR.'test.py' => 'test.py',
+ $tmpDir.DIRECTORY_SEPARATOR.'foo' => 'foo',
+ $tmpDir.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'bar.tmp' => 'foo'.DIRECTORY_SEPARATOR.'bar.tmp',
+ $tmpDir.DIRECTORY_SEPARATOR.'test.php' => 'test.php',
+ $tmpDir.DIRECTORY_SEPARATOR.'toto' => 'toto',
+ ),
+ ),
+ );
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FileTypeFilterIteratorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FileTypeFilterIteratorTest.php
new file mode 100644
index 0000000..cfa8684
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FileTypeFilterIteratorTest.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+use Symfony\Component\Finder\Iterator\FileTypeFilterIterator;
+
+class FileTypeFilterIteratorTest extends RealIteratorTestCase
+{
+ /**
+ * @dataProvider getAcceptData
+ */
+ public function testAccept($mode, $expected)
+ {
+ $inner = new InnerTypeIterator(self::$files);
+
+ $iterator = new FileTypeFilterIterator($inner, $mode);
+
+ $this->assertIterator($expected, $iterator);
+ }
+
+ public function getAcceptData()
+ {
+ $onlyFiles = array(
+ 'test.py',
+ 'foo/bar.tmp',
+ 'test.php',
+ '.bar',
+ '.foo/.bar',
+ '.foo/bar',
+ 'foo bar',
+ );
+
+ $onlyDirectories = array(
+ '.git',
+ 'foo',
+ 'toto',
+ '.foo',
+ );
+
+ return array(
+ array(FileTypeFilterIterator::ONLY_FILES, $this->toAbsolute($onlyFiles)),
+ array(FileTypeFilterIterator::ONLY_DIRECTORIES, $this->toAbsolute($onlyDirectories)),
+ );
+ }
+}
+
+class InnerTypeIterator extends \ArrayIterator
+{
+ public function current()
+ {
+ return new \SplFileInfo(parent::current());
+ }
+
+ public function isFile()
+ {
+ return $this->current()->isFile();
+ }
+
+ public function isDir()
+ {
+ return $this->current()->isDir();
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilecontentFilterIteratorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilecontentFilterIteratorTest.php
new file mode 100644
index 0000000..744bdae
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilecontentFilterIteratorTest.php
@@ -0,0 +1,86 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+use Symfony\Component\Finder\Iterator\FilecontentFilterIterator;
+
+class FilecontentFilterIteratorTest extends IteratorTestCase
+{
+ public function testAccept()
+ {
+ $inner = new MockFileListIterator(array('test.txt'));
+ $iterator = new FilecontentFilterIterator($inner, array(), array());
+ $this->assertIterator(array('test.txt'), $iterator);
+ }
+
+ public function testDirectory()
+ {
+ $inner = new MockFileListIterator(array('directory'));
+ $iterator = new FilecontentFilterIterator($inner, array('directory'), array());
+ $this->assertIterator(array(), $iterator);
+ }
+
+ public function testUnreadableFile()
+ {
+ $inner = new MockFileListIterator(array('file r-'));
+ $iterator = new FilecontentFilterIterator($inner, array('file r-'), array());
+ $this->assertIterator(array(), $iterator);
+ }
+
+ /**
+ * @dataProvider getTestFilterData
+ */
+ public function testFilter(\Iterator $inner, array $matchPatterns, array $noMatchPatterns, array $resultArray)
+ {
+ $iterator = new FilecontentFilterIterator($inner, $matchPatterns, $noMatchPatterns);
+ $this->assertIterator($resultArray, $iterator);
+ }
+
+ public function getTestFilterData()
+ {
+ $inner = new MockFileListIterator();
+
+ $inner[] = new MockSplFileInfo(array(
+ 'name' => 'a.txt',
+ 'contents' => 'Lorem ipsum...',
+ 'type' => 'file',
+ 'mode' => 'r+', )
+ );
+
+ $inner[] = new MockSplFileInfo(array(
+ 'name' => 'b.yml',
+ 'contents' => 'dolor sit...',
+ 'type' => 'file',
+ 'mode' => 'r+', )
+ );
+
+ $inner[] = new MockSplFileInfo(array(
+ 'name' => 'some/other/dir/third.php',
+ 'contents' => 'amet...',
+ 'type' => 'file',
+ 'mode' => 'r+', )
+ );
+
+ $inner[] = new MockSplFileInfo(array(
+ 'name' => 'unreadable-file.txt',
+ 'contents' => false,
+ 'type' => 'file',
+ 'mode' => 'r+', )
+ );
+
+ return array(
+ array($inner, array('.'), array(), array('a.txt', 'b.yml', 'some/other/dir/third.php')),
+ array($inner, array('ipsum'), array(), array('a.txt')),
+ array($inner, array('i', 'amet'), array('Lorem', 'amet'), array('b.yml')),
+ );
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilenameFilterIteratorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilenameFilterIteratorTest.php
new file mode 100644
index 0000000..c4b9795
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilenameFilterIteratorTest.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+use Symfony\Component\Finder\Iterator\FilenameFilterIterator;
+
+class FilenameFilterIteratorTest extends IteratorTestCase
+{
+ /**
+ * @dataProvider getAcceptData
+ */
+ public function testAccept($matchPatterns, $noMatchPatterns, $expected)
+ {
+ $inner = new InnerNameIterator(array('test.php', 'test.py', 'foo.php'));
+
+ $iterator = new FilenameFilterIterator($inner, $matchPatterns, $noMatchPatterns);
+
+ $this->assertIterator($expected, $iterator);
+ }
+
+ public function getAcceptData()
+ {
+ return array(
+ array(array('test.*'), array(), array('test.php', 'test.py')),
+ array(array(), array('test.*'), array('foo.php')),
+ array(array('*.php'), array('test.*'), array('foo.php')),
+ array(array('*.php', '*.py'), array('foo.*'), array('test.php', 'test.py')),
+ array(array('/\.php$/'), array(), array('test.php', 'foo.php')),
+ array(array(), array('/\.php$/'), array('test.py')),
+ );
+ }
+}
+
+class InnerNameIterator extends \ArrayIterator
+{
+ public function current()
+ {
+ return new \SplFileInfo(parent::current());
+ }
+
+ public function getFilename()
+ {
+ return parent::current();
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilterIteratorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilterIteratorTest.php
new file mode 100644
index 0000000..029a266
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilterIteratorTest.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+/**
+ * @author Alex Bogomazov
+ */
+class FilterIteratorTest extends RealIteratorTestCase
+{
+ public function testFilterFilesystemIterators()
+ {
+ $i = new \FilesystemIterator($this->toAbsolute());
+
+ // it is expected that there are test.py test.php in the tmpDir
+ $i = $this->getMockForAbstractClass('Symfony\Component\Finder\Iterator\FilterIterator', array($i));
+ $i->expects($this->any())
+ ->method('accept')
+ ->will($this->returnCallback(function () use ($i) {
+ return (bool) preg_match('/\.php/', (string) $i->current());
+ })
+ );
+
+ $c = 0;
+ foreach ($i as $item) {
+ $c++;
+ }
+
+ $this->assertEquals(1, $c);
+
+ $i->rewind();
+
+ $c = 0;
+ foreach ($i as $item) {
+ $c++;
+ }
+
+ // This would fail with \FilterIterator but works with Symfony\Component\Finder\Iterator\FilterIterator
+ // see https://bugs.php.net/bug.php?id=49104
+ $this->assertEquals(1, $c);
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/Iterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/Iterator.php
new file mode 100644
index 0000000..849bf08
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/Iterator.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+class Iterator implements \Iterator
+{
+ protected $values = array();
+
+ public function __construct(array $values = array())
+ {
+ foreach ($values as $value) {
+ $this->attach(new \SplFileInfo($value));
+ }
+ $this->rewind();
+ }
+
+ public function attach(\SplFileInfo $fileinfo)
+ {
+ $this->values[] = $fileinfo;
+ }
+
+ public function rewind()
+ {
+ reset($this->values);
+ }
+
+ public function valid()
+ {
+ return false !== $this->current();
+ }
+
+ public function next()
+ {
+ next($this->values);
+ }
+
+ public function current()
+ {
+ return current($this->values);
+ }
+
+ public function key()
+ {
+ return key($this->values);
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php
new file mode 100644
index 0000000..ae7388e
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php
@@ -0,0 +1,98 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+abstract class IteratorTestCase extends \PHPUnit_Framework_TestCase
+{
+ protected function assertIterator($expected, \Traversable $iterator)
+ {
+ // set iterator_to_array $use_key to false to avoid values merge
+ // this made FinderTest::testAppendWithAnArray() failed with GnuFinderAdapter
+ $values = array_map(function (\SplFileInfo $fileinfo) { return str_replace('/', DIRECTORY_SEPARATOR, $fileinfo->getPathname()); }, iterator_to_array($iterator, false));
+
+ $expected = array_map(function ($path) { return str_replace('/', DIRECTORY_SEPARATOR, $path); }, $expected);
+
+ sort($values);
+ sort($expected);
+
+ $this->assertEquals($expected, array_values($values));
+ }
+
+ protected function assertOrderedIterator($expected, \Traversable $iterator)
+ {
+ $values = array_map(function (\SplFileInfo $fileinfo) { return $fileinfo->getPathname(); }, iterator_to_array($iterator));
+
+ $this->assertEquals($expected, array_values($values));
+ }
+
+ /**
+ * Same as assertOrderedIterator, but checks the order of groups of
+ * array elements.
+ *
+ * @param array $expected - an array of arrays. For any two subarrays
+ * $a and $b such that $a goes before $b in $expected, the method
+ * asserts that any element of $a goes before any element of $b
+ * in the sequence generated by $iterator
+ * @param \Traversable $iterator
+ */
+ protected function assertOrderedIteratorForGroups($expected, \Traversable $iterator)
+ {
+ $values = array_values(array_map(function (\SplFileInfo $fileinfo) { return $fileinfo->getPathname(); }, iterator_to_array($iterator)));
+
+ foreach ($expected as $subarray) {
+ $temp = array();
+ while (count($values) && count($temp) < count($subarray)) {
+ array_push($temp, array_shift($values));
+ }
+ sort($temp);
+ sort($subarray);
+ $this->assertEquals($subarray, $temp);
+ }
+ }
+
+ /**
+ * Same as IteratorTestCase::assertIterator with foreach usage.
+ *
+ * @param array $expected
+ * @param \Traversable $iterator
+ */
+ protected function assertIteratorInForeach($expected, \Traversable $iterator)
+ {
+ $values = array();
+ foreach ($iterator as $file) {
+ $this->assertInstanceOf('Symfony\\Component\\Finder\\SplFileInfo', $file);
+ $values[] = $file->getPathname();
+ }
+
+ sort($values);
+ sort($expected);
+
+ $this->assertEquals($expected, array_values($values));
+ }
+
+ /**
+ * Same as IteratorTestCase::assertOrderedIterator with foreach usage.
+ *
+ * @param array $expected
+ * @param \Traversable $iterator
+ */
+ protected function assertOrderedIteratorInForeach($expected, \Traversable $iterator)
+ {
+ $values = array();
+ foreach ($iterator as $file) {
+ $this->assertInstanceOf('Symfony\\Component\\Finder\\SplFileInfo', $file);
+ $values[] = $file->getPathname();
+ }
+
+ $this->assertEquals($expected, array_values($values));
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php
new file mode 100644
index 0000000..eb0adfa
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+class MockFileListIterator extends \ArrayIterator
+{
+ public function __construct(array $filesArray = array())
+ {
+ $files = array_map(function ($file) { return new MockSplFileInfo($file); }, $filesArray);
+ parent::__construct($files);
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php
new file mode 100644
index 0000000..f2e8f8e
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php
@@ -0,0 +1,134 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+class MockSplFileInfo extends \SplFileInfo
+{
+ const TYPE_DIRECTORY = 1;
+ const TYPE_FILE = 2;
+ const TYPE_UNKNOWN = 3;
+
+ private $contents = null;
+ private $mode = null;
+ private $type = null;
+ private $relativePath = null;
+ private $relativePathname = null;
+
+ public function __construct($param)
+ {
+ if (is_string($param)) {
+ parent::__construct($param);
+ } elseif (is_array($param)) {
+ $defaults = array(
+ 'name' => 'file.txt',
+ 'contents' => null,
+ 'mode' => null,
+ 'type' => null,
+ 'relativePath' => null,
+ 'relativePathname' => null,
+ );
+ $defaults = array_merge($defaults, $param);
+ parent::__construct($defaults['name']);
+ $this->setContents($defaults['contents']);
+ $this->setMode($defaults['mode']);
+ $this->setType($defaults['type']);
+ $this->setRelativePath($defaults['relativePath']);
+ $this->setRelativePathname($defaults['relativePathname']);
+ } else {
+ throw new \RuntimeException(sprintf('Incorrect parameter "%s"', $param));
+ }
+ }
+
+ public function isFile()
+ {
+ if (null === $this->type) {
+ return preg_match('/file/', $this->getFilename());
+ };
+
+ return self::TYPE_FILE === $this->type;
+ }
+
+ public function isDir()
+ {
+ if (null === $this->type) {
+ return preg_match('/directory/', $this->getFilename());
+ }
+
+ return self::TYPE_DIRECTORY === $this->type;
+ }
+
+ public function isReadable()
+ {
+ if (null === $this->mode) {
+ return preg_match('/r\+/', $this->getFilename());
+ }
+
+ return preg_match('/r\+/', $this->mode);
+ }
+
+ public function getContents()
+ {
+ return $this->contents;
+ }
+
+ public function setContents($contents)
+ {
+ $this->contents = $contents;
+ }
+
+ public function setMode($mode)
+ {
+ $this->mode = $mode;
+ }
+
+ public function setType($type)
+ {
+ if (is_string($type)) {
+ switch ($type) {
+ case 'directory':
+ $this->type = self::TYPE_DIRECTORY;
+ case 'd':
+ $this->type = self::TYPE_DIRECTORY;
+ break;
+ case 'file':
+ $this->type = self::TYPE_FILE;
+ case 'f':
+ $this->type = self::TYPE_FILE;
+ break;
+ default:
+ $this->type = self::TYPE_UNKNOWN;
+ }
+ } else {
+ $this->type = $type;
+ }
+ }
+
+ public function setRelativePath($relativePath)
+ {
+ $this->relativePath = $relativePath;
+ }
+
+ public function setRelativePathname($relativePathname)
+ {
+ $this->relativePathname = $relativePathname;
+ }
+
+ public function getRelativePath()
+ {
+ return $this->relativePath;
+ }
+
+ public function getRelativePathname()
+ {
+ return $this->relativePathname;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MultiplePcreFilterIteratorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MultiplePcreFilterIteratorTest.php
new file mode 100644
index 0000000..89d8edb
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MultiplePcreFilterIteratorTest.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+use Symfony\Component\Finder\Iterator\MultiplePcreFilterIterator;
+
+class MultiplePcreFilterIteratorTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider getIsRegexFixtures
+ */
+ public function testIsRegex($string, $isRegex, $message)
+ {
+ $testIterator = new TestMultiplePcreFilterIterator();
+ $this->assertEquals($isRegex, $testIterator->isRegex($string), $message);
+ }
+
+ public function getIsRegexFixtures()
+ {
+ return array(
+ array('foo', false, 'string'),
+ array(' foo ', false, '" " is not a valid delimiter'),
+ array('\\foo\\', false, '"\\" is not a valid delimiter'),
+ array('afooa', false, '"a" is not a valid delimiter'),
+ array('//', false, 'the pattern should contain at least 1 character'),
+ array('/a/', true, 'valid regex'),
+ array('/foo/', true, 'valid regex'),
+ array('/foo/i', true, 'valid regex with a single modifier'),
+ array('/foo/imsxu', true, 'valid regex with multiple modifiers'),
+ array('#foo#', true, '"#" is a valid delimiter'),
+ array('{foo}', true, '"{,}" is a valid delimiter pair'),
+ array('*foo.*', false, '"*" is not considered as a valid delimiter'),
+ array('?foo.?', false, '"?" is not considered as a valid delimiter'),
+ );
+ }
+}
+
+class TestMultiplePcreFilterIterator extends MultiplePcreFilterIterator
+{
+ public function __construct()
+ {
+ }
+
+ public function accept()
+ {
+ throw new \BadFunctionCallException('Not implemented');
+ }
+
+ public function isRegex($str)
+ {
+ return parent::isRegex($str);
+ }
+
+ public function toRegex($str)
+ {
+ throw new \BadFunctionCallException('Not implemented');
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/PathFilterIteratorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/PathFilterIteratorTest.php
new file mode 100644
index 0000000..579beed
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/PathFilterIteratorTest.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+use Symfony\Component\Finder\Iterator\PathFilterIterator;
+
+class PathFilterIteratorTest extends IteratorTestCase
+{
+ /**
+ * @dataProvider getTestFilterData
+ */
+ public function testFilter(\Iterator $inner, array $matchPatterns, array $noMatchPatterns, array $resultArray)
+ {
+ $iterator = new PathFilterIterator($inner, $matchPatterns, $noMatchPatterns);
+ $this->assertIterator($resultArray, $iterator);
+ }
+
+ public function getTestFilterData()
+ {
+ $inner = new MockFileListIterator();
+
+ //PATH: A/B/C/abc.dat
+ $inner[] = new MockSplFileInfo(array(
+ 'name' => 'abc.dat',
+ 'relativePathname' => 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat',
+ ));
+
+ //PATH: A/B/ab.dat
+ $inner[] = new MockSplFileInfo(array(
+ 'name' => 'ab.dat',
+ 'relativePathname' => 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat',
+ ));
+
+ //PATH: A/a.dat
+ $inner[] = new MockSplFileInfo(array(
+ 'name' => 'a.dat',
+ 'relativePathname' => 'A'.DIRECTORY_SEPARATOR.'a.dat',
+ ));
+
+ //PATH: copy/A/B/C/abc.dat.copy
+ $inner[] = new MockSplFileInfo(array(
+ 'name' => 'abc.dat.copy',
+ 'relativePathname' => 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat',
+ ));
+
+ //PATH: copy/A/B/ab.dat.copy
+ $inner[] = new MockSplFileInfo(array(
+ 'name' => 'ab.dat.copy',
+ 'relativePathname' => 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat',
+ ));
+
+ //PATH: copy/A/a.dat.copy
+ $inner[] = new MockSplFileInfo(array(
+ 'name' => 'a.dat.copy',
+ 'relativePathname' => 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'a.dat',
+ ));
+
+ return array(
+ array($inner, array('/^A/'), array(), array('abc.dat', 'ab.dat', 'a.dat')),
+ array($inner, array('/^A\/B/'), array(), array('abc.dat', 'ab.dat')),
+ array($inner, array('/^A\/B\/C/'), array(), array('abc.dat')),
+ array($inner, array('/A\/B\/C/'), array(), array('abc.dat', 'abc.dat.copy')),
+
+ array($inner, array('A'), array(), array('abc.dat', 'ab.dat', 'a.dat', 'abc.dat.copy', 'ab.dat.copy', 'a.dat.copy')),
+ array($inner, array('A/B'), array(), array('abc.dat', 'ab.dat', 'abc.dat.copy', 'ab.dat.copy')),
+ array($inner, array('A/B/C'), array(), array('abc.dat', 'abc.dat.copy')),
+
+ array($inner, array('copy/A'), array(), array('abc.dat.copy', 'ab.dat.copy', 'a.dat.copy')),
+ array($inner, array('copy/A/B'), array(), array('abc.dat.copy', 'ab.dat.copy')),
+ array($inner, array('copy/A/B/C'), array(), array('abc.dat.copy')),
+
+ );
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/RealIteratorTestCase.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/RealIteratorTestCase.php
new file mode 100644
index 0000000..e22476d
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/RealIteratorTestCase.php
@@ -0,0 +1,109 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+abstract class RealIteratorTestCase extends IteratorTestCase
+{
+ protected static $tmpDir;
+ protected static $files;
+
+ public static function setUpBeforeClass()
+ {
+ self::$tmpDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'symfony_finder';
+
+ self::$files = array(
+ '.git/',
+ '.foo/',
+ '.foo/.bar',
+ '.foo/bar',
+ '.bar',
+ 'test.py',
+ 'foo/',
+ 'foo/bar.tmp',
+ 'test.php',
+ 'toto/',
+ 'foo bar',
+ );
+
+ self::$files = self::toAbsolute(self::$files);
+
+ if (is_dir(self::$tmpDir)) {
+ self::tearDownAfterClass();
+ } else {
+ mkdir(self::$tmpDir);
+ }
+
+ foreach (self::$files as $file) {
+ if (DIRECTORY_SEPARATOR === $file[strlen($file) - 1]) {
+ mkdir($file);
+ } else {
+ touch($file);
+ }
+ }
+
+ file_put_contents(self::toAbsolute('test.php'), str_repeat(' ', 800));
+ file_put_contents(self::toAbsolute('test.py'), str_repeat(' ', 2000));
+
+ touch(self::toAbsolute('foo/bar.tmp'), strtotime('2005-10-15'));
+ touch(self::toAbsolute('test.php'), strtotime('2005-10-15'));
+ }
+
+ public static function tearDownAfterClass()
+ {
+ foreach (array_reverse(self::$files) as $file) {
+ if (DIRECTORY_SEPARATOR === $file[strlen($file) - 1]) {
+ @rmdir($file);
+ } else {
+ @unlink($file);
+ }
+ }
+ }
+
+ protected static function toAbsolute($files = null)
+ {
+ /*
+ * Without the call to setUpBeforeClass() property can be null.
+ */
+ if (!self::$tmpDir) {
+ self::$tmpDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'symfony_finder';
+ }
+
+ if (is_array($files)) {
+ $f = array();
+ foreach ($files as $file) {
+ if (is_array($file)) {
+ $f[] = self::toAbsolute($file);
+ } else {
+ $f[] = self::$tmpDir.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, $file);
+ }
+ }
+
+ return $f;
+ }
+
+ if (is_string($files)) {
+ return self::$tmpDir.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, $files);
+ }
+
+ return self::$tmpDir;
+ }
+
+ protected static function toAbsoluteFixtures($files)
+ {
+ $f = array();
+ foreach ($files as $file) {
+ $f[] = realpath(__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.$file);
+ }
+
+ return $f;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php
new file mode 100644
index 0000000..412054b
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php
@@ -0,0 +1,83 @@
+
+*
+* For the full copyright and license information, please view the LICENSE
+* file that was distributed with this source code.
+*/
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator;
+
+class RecursiveDirectoryIteratorTest extends IteratorTestCase
+{
+ /**
+ * @dataProvider getPaths
+ *
+ * @param string $path
+ * @param bool $seekable
+ * @param array $contains
+ * @param string $message
+ */
+ public function testRewind($path, $seekable, $contains, $message = null)
+ {
+ try {
+ $i = new RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
+ } catch (\UnexpectedValueException $e) {
+ $this->markTestSkipped(sprintf('Unsupported stream "%s".', $path));
+ }
+
+ $i->rewind();
+
+ $this->assertTrue(true, $message);
+ }
+
+ /**
+ * @dataProvider getPaths
+ *
+ * @param string $path
+ * @param bool $seekable
+ * @param array $contains
+ * @param string $message
+ */
+ public function testSeek($path, $seekable, $contains, $message = null)
+ {
+ try {
+ $i = new RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
+ } catch (\UnexpectedValueException $e) {
+ $this->markTestSkipped(sprintf('Unsupported stream "%s".', $path));
+ }
+
+ $actual = array();
+
+ $i->seek(0);
+ $actual[] = $i->getPathname();
+
+ $i->seek(1);
+ $actual[] = $i->getPathname();
+
+ $i->seek(2);
+ $actual[] = $i->getPathname();
+
+ $this->assertEquals($contains, $actual);
+ }
+
+ public function getPaths()
+ {
+ $data = array();
+
+ // ftp
+ $contains = array(
+ 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'README',
+ 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'index.html',
+ 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'pub',
+ );
+ $data[] = array('ftp://ftp.mozilla.org/', false, $contains);
+
+ return $data;
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/SizeRangeFilterIteratorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/SizeRangeFilterIteratorTest.php
new file mode 100644
index 0000000..8780db4
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/SizeRangeFilterIteratorTest.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator;
+use Symfony\Component\Finder\Comparator\NumberComparator;
+
+class SizeRangeFilterIteratorTest extends RealIteratorTestCase
+{
+ /**
+ * @dataProvider getAcceptData
+ */
+ public function testAccept($size, $expected)
+ {
+ $inner = new InnerSizeIterator(self::$files);
+
+ $iterator = new SizeRangeFilterIterator($inner, $size);
+
+ $this->assertIterator($expected, $iterator);
+ }
+
+ public function getAcceptData()
+ {
+ $lessThan1KGreaterThan05K = array(
+ '.foo',
+ '.git',
+ 'foo',
+ 'test.php',
+ 'toto',
+ );
+
+ return array(
+ array(array(new NumberComparator('< 1K'), new NumberComparator('> 0.5K')), $this->toAbsolute($lessThan1KGreaterThan05K)),
+ );
+ }
+}
+
+class InnerSizeIterator extends \ArrayIterator
+{
+ public function current()
+ {
+ return new \SplFileInfo(parent::current());
+ }
+
+ public function getFilename()
+ {
+ return parent::current();
+ }
+
+ public function isFile()
+ {
+ return $this->current()->isFile();
+ }
+
+ public function getSize()
+ {
+ return $this->current()->getSize();
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php
new file mode 100644
index 0000000..e2f433f
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php
@@ -0,0 +1,169 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Tests\Iterator;
+
+use Symfony\Component\Finder\Iterator\SortableIterator;
+
+class SortableIteratorTest extends RealIteratorTestCase
+{
+ public function testConstructor()
+ {
+ try {
+ new SortableIterator(new Iterator(array()), 'foobar');
+ $this->fail('__construct() throws an \InvalidArgumentException exception if the mode is not valid');
+ } catch (\Exception $e) {
+ $this->assertInstanceOf('InvalidArgumentException', $e, '__construct() throws an \InvalidArgumentException exception if the mode is not valid');
+ }
+ }
+
+ /**
+ * @dataProvider getAcceptData
+ */
+ public function testAccept($mode, $expected)
+ {
+ if (!is_callable($mode)) {
+ switch ($mode) {
+ case SortableIterator::SORT_BY_ACCESSED_TIME :
+ file_get_contents(self::toAbsolute('.git'));
+ sleep(1);
+ file_get_contents(self::toAbsolute('.bar'));
+ break;
+ case SortableIterator::SORT_BY_CHANGED_TIME :
+ file_put_contents(self::toAbsolute('test.php'), 'foo');
+ sleep(1);
+ file_put_contents(self::toAbsolute('test.py'), 'foo');
+ break;
+ case SortableIterator::SORT_BY_MODIFIED_TIME :
+ file_put_contents(self::toAbsolute('test.php'), 'foo');
+ sleep(1);
+ file_put_contents(self::toAbsolute('test.py'), 'foo');
+ break;
+ }
+ }
+
+ $inner = new Iterator(self::$files);
+
+ $iterator = new SortableIterator($inner, $mode);
+
+ if ($mode === SortableIterator::SORT_BY_ACCESSED_TIME
+ || $mode === SortableIterator::SORT_BY_CHANGED_TIME
+ || $mode === SortableIterator::SORT_BY_MODIFIED_TIME) {
+ $this->assertOrderedIteratorForGroups($expected, $iterator);
+ } else {
+ $this->assertOrderedIterator($expected, $iterator);
+ }
+ }
+
+ public function getAcceptData()
+ {
+ $sortByName = array(
+ '.bar',
+ '.foo',
+ '.foo/.bar',
+ '.foo/bar',
+ '.git',
+ 'foo',
+ 'foo bar',
+ 'foo/bar.tmp',
+ 'test.php',
+ 'test.py',
+ 'toto',
+ );
+
+ $sortByType = array(
+ '.foo',
+ '.git',
+ 'foo',
+ 'toto',
+ '.bar',
+ '.foo/.bar',
+ '.foo/bar',
+ 'foo bar',
+ 'foo/bar.tmp',
+ 'test.php',
+ 'test.py',
+ );
+
+ $customComparison = array(
+ '.bar',
+ '.foo',
+ '.foo/.bar',
+ '.foo/bar',
+ '.git',
+ 'foo',
+ 'foo bar',
+ 'foo/bar.tmp',
+ 'test.php',
+ 'test.py',
+ 'toto',
+ );
+
+ $sortByAccessedTime = array(
+ // For these two files the access time was set to 2005-10-15
+ array('foo/bar.tmp', 'test.php'),
+ // These files were created more or less at the same time
+ array(
+ '.git',
+ '.foo',
+ '.foo/.bar',
+ '.foo/bar',
+ 'test.py',
+ 'foo',
+ 'toto',
+ 'foo bar',
+ ),
+ // This file was accessed after sleeping for 1 sec
+ array('.bar'),
+ );
+
+ $sortByChangedTime = array(
+ array(
+ '.git',
+ '.foo',
+ '.foo/.bar',
+ '.foo/bar',
+ '.bar',
+ 'foo',
+ 'foo/bar.tmp',
+ 'toto',
+ 'foo bar',
+ ),
+ array('test.php'),
+ array('test.py'),
+ );
+
+ $sortByModifiedTime = array(
+ array(
+ '.git',
+ '.foo',
+ '.foo/.bar',
+ '.foo/bar',
+ '.bar',
+ 'foo',
+ 'foo/bar.tmp',
+ 'toto',
+ 'foo bar',
+ ),
+ array('test.php'),
+ array('test.py'),
+ );
+
+ return array(
+ array(SortableIterator::SORT_BY_NAME, $this->toAbsolute($sortByName)),
+ array(SortableIterator::SORT_BY_TYPE, $this->toAbsolute($sortByType)),
+ array(SortableIterator::SORT_BY_ACCESSED_TIME, $this->toAbsolute($sortByAccessedTime)),
+ array(SortableIterator::SORT_BY_CHANGED_TIME, $this->toAbsolute($sortByChangedTime)),
+ array(SortableIterator::SORT_BY_MODIFIED_TIME, $this->toAbsolute($sortByModifiedTime)),
+ array(function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealpath(), $b->getRealpath()); }, $this->toAbsolute($customComparison)),
+ );
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/composer.json b/console/skel/symfony/finder/Symfony/Component/Finder/composer.json
new file mode 100644
index 0000000..b6e6997
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/composer.json
@@ -0,0 +1,31 @@
+{
+ "name": "symfony/finder",
+ "type": "library",
+ "description": "Symfony Finder Component",
+ "keywords": [],
+ "homepage": "http://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "http://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "autoload": {
+ "psr-0": { "Symfony\\Component\\Finder\\": "" }
+ },
+ "target-dir": "Symfony/Component/Finder",
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.5-dev"
+ }
+ }
+}
diff --git a/console/skel/symfony/finder/Symfony/Component/Finder/phpunit.xml.dist b/console/skel/symfony/finder/Symfony/Component/Finder/phpunit.xml.dist
new file mode 100644
index 0000000..0ed1223
--- /dev/null
+++ b/console/skel/symfony/finder/Symfony/Component/Finder/phpunit.xml.dist
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/console/skel/symfony/http-foundation/.gitignore b/console/skel/symfony/http-foundation/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/console/skel/symfony/http-foundation/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/console/skel/symfony/http-foundation/AcceptHeader.php b/console/skel/symfony/http-foundation/AcceptHeader.php
new file mode 100644
index 0000000..d174026
--- /dev/null
+++ b/console/skel/symfony/http-foundation/AcceptHeader.php
@@ -0,0 +1,168 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Represents an Accept-* header.
+ *
+ * An accept header is compound with a list of items,
+ * sorted by descending quality.
+ *
+ * @author Jean-François Simon
+ */
+class AcceptHeader
+{
+ /**
+ * @var AcceptHeaderItem[]
+ */
+ private $items = array();
+
+ /**
+ * @var bool
+ */
+ private $sorted = true;
+
+ /**
+ * @param AcceptHeaderItem[] $items
+ */
+ public function __construct(array $items)
+ {
+ foreach ($items as $item) {
+ $this->add($item);
+ }
+ }
+
+ /**
+ * Builds an AcceptHeader instance from a string.
+ *
+ * @param string $headerValue
+ *
+ * @return self
+ */
+ public static function fromString($headerValue)
+ {
+ $index = 0;
+
+ return new self(array_map(function ($itemValue) use (&$index) {
+ $item = AcceptHeaderItem::fromString($itemValue);
+ $item->setIndex($index++);
+
+ return $item;
+ }, preg_split('/\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\s*/', $headerValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)));
+ }
+
+ /**
+ * Returns header value's string representation.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return implode(',', $this->items);
+ }
+
+ /**
+ * Tests if header has given value.
+ *
+ * @param string $value
+ *
+ * @return bool
+ */
+ public function has($value)
+ {
+ return isset($this->items[$value]);
+ }
+
+ /**
+ * Returns given value's item, if exists.
+ *
+ * @param string $value
+ *
+ * @return AcceptHeaderItem|null
+ */
+ public function get($value)
+ {
+ return isset($this->items[$value]) ? $this->items[$value] : null;
+ }
+
+ /**
+ * Adds an item.
+ *
+ * @return $this
+ */
+ public function add(AcceptHeaderItem $item)
+ {
+ $this->items[$item->getValue()] = $item;
+ $this->sorted = false;
+
+ return $this;
+ }
+
+ /**
+ * Returns all items.
+ *
+ * @return AcceptHeaderItem[]
+ */
+ public function all()
+ {
+ $this->sort();
+
+ return $this->items;
+ }
+
+ /**
+ * Filters items on their value using given regex.
+ *
+ * @param string $pattern
+ *
+ * @return self
+ */
+ public function filter($pattern)
+ {
+ return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) {
+ return preg_match($pattern, $item->getValue());
+ }));
+ }
+
+ /**
+ * Returns first item.
+ *
+ * @return AcceptHeaderItem|null
+ */
+ public function first()
+ {
+ $this->sort();
+
+ return !empty($this->items) ? reset($this->items) : null;
+ }
+
+ /**
+ * Sorts items by descending quality.
+ */
+ private function sort()
+ {
+ if (!$this->sorted) {
+ uasort($this->items, function (AcceptHeaderItem $a, AcceptHeaderItem $b) {
+ $qA = $a->getQuality();
+ $qB = $b->getQuality();
+
+ if ($qA === $qB) {
+ return $a->getIndex() > $b->getIndex() ? 1 : -1;
+ }
+
+ return $qA > $qB ? -1 : 1;
+ });
+
+ $this->sorted = true;
+ }
+ }
+}
diff --git a/console/skel/symfony/http-foundation/AcceptHeaderItem.php b/console/skel/symfony/http-foundation/AcceptHeaderItem.php
new file mode 100644
index 0000000..c950657
--- /dev/null
+++ b/console/skel/symfony/http-foundation/AcceptHeaderItem.php
@@ -0,0 +1,209 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Represents an Accept-* header item.
+ *
+ * @author Jean-François Simon
+ */
+class AcceptHeaderItem
+{
+ private $value;
+ private $quality = 1.0;
+ private $index = 0;
+ private $attributes = array();
+
+ /**
+ * @param string $value
+ * @param array $attributes
+ */
+ public function __construct($value, array $attributes = array())
+ {
+ $this->value = $value;
+ foreach ($attributes as $name => $value) {
+ $this->setAttribute($name, $value);
+ }
+ }
+
+ /**
+ * Builds an AcceptHeaderInstance instance from a string.
+ *
+ * @param string $itemValue
+ *
+ * @return self
+ */
+ public static function fromString($itemValue)
+ {
+ $bits = preg_split('/\s*(?:;*("[^"]+");*|;*(\'[^\']+\');*|;+)\s*/', $itemValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+ $value = array_shift($bits);
+ $attributes = array();
+
+ $lastNullAttribute = null;
+ foreach ($bits as $bit) {
+ if (($start = substr($bit, 0, 1)) === ($end = substr($bit, -1)) && ('"' === $start || '\'' === $start)) {
+ $attributes[$lastNullAttribute] = substr($bit, 1, -1);
+ } elseif ('=' === $end) {
+ $lastNullAttribute = $bit = substr($bit, 0, -1);
+ $attributes[$bit] = null;
+ } else {
+ $parts = explode('=', $bit);
+ $attributes[$parts[0]] = isset($parts[1]) && \strlen($parts[1]) > 0 ? $parts[1] : '';
+ }
+ }
+
+ return new self(($start = substr($value, 0, 1)) === ($end = substr($value, -1)) && ('"' === $start || '\'' === $start) ? substr($value, 1, -1) : $value, $attributes);
+ }
+
+ /**
+ * Returns header value's string representation.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $string = $this->value.($this->quality < 1 ? ';q='.$this->quality : '');
+ if (\count($this->attributes) > 0) {
+ $string .= ';'.implode(';', array_map(function ($name, $value) {
+ return sprintf(preg_match('/[,;=]/', $value) ? '%s="%s"' : '%s=%s', $name, $value);
+ }, array_keys($this->attributes), $this->attributes));
+ }
+
+ return $string;
+ }
+
+ /**
+ * Set the item value.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setValue($value)
+ {
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * Returns the item value.
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Set the item quality.
+ *
+ * @param float $quality
+ *
+ * @return $this
+ */
+ public function setQuality($quality)
+ {
+ $this->quality = $quality;
+
+ return $this;
+ }
+
+ /**
+ * Returns the item quality.
+ *
+ * @return float
+ */
+ public function getQuality()
+ {
+ return $this->quality;
+ }
+
+ /**
+ * Set the item index.
+ *
+ * @param int $index
+ *
+ * @return $this
+ */
+ public function setIndex($index)
+ {
+ $this->index = $index;
+
+ return $this;
+ }
+
+ /**
+ * Returns the item index.
+ *
+ * @return int
+ */
+ public function getIndex()
+ {
+ return $this->index;
+ }
+
+ /**
+ * Tests if an attribute exists.
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function hasAttribute($name)
+ {
+ return isset($this->attributes[$name]);
+ }
+
+ /**
+ * Returns an attribute by its name.
+ *
+ * @param string $name
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function getAttribute($name, $default = null)
+ {
+ return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
+ }
+
+ /**
+ * Returns all attributes.
+ *
+ * @return array
+ */
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * Set an attribute.
+ *
+ * @param string $name
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setAttribute($name, $value)
+ {
+ if ('q' === $name) {
+ $this->quality = (float) $value;
+ } else {
+ $this->attributes[$name] = (string) $value;
+ }
+
+ return $this;
+ }
+}
diff --git a/console/skel/symfony/http-foundation/ApacheRequest.php b/console/skel/symfony/http-foundation/ApacheRequest.php
new file mode 100644
index 0000000..4e99186
--- /dev/null
+++ b/console/skel/symfony/http-foundation/ApacheRequest.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Request represents an HTTP request from an Apache server.
+ *
+ * @author Fabien Potencier
+ */
+class ApacheRequest extends Request
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function prepareRequestUri()
+ {
+ return $this->server->get('REQUEST_URI');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function prepareBaseUrl()
+ {
+ $baseUrl = $this->server->get('SCRIPT_NAME');
+
+ if (false === strpos($this->server->get('REQUEST_URI'), $baseUrl)) {
+ // assume mod_rewrite
+ return rtrim(\dirname($baseUrl), '/\\');
+ }
+
+ return $baseUrl;
+ }
+}
diff --git a/console/skel/symfony/http-foundation/BinaryFileResponse.php b/console/skel/symfony/http-foundation/BinaryFileResponse.php
new file mode 100644
index 0000000..f115159
--- /dev/null
+++ b/console/skel/symfony/http-foundation/BinaryFileResponse.php
@@ -0,0 +1,357 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileException;
+use Symfony\Component\HttpFoundation\File\File;
+
+/**
+ * BinaryFileResponse represents an HTTP response delivering a file.
+ *
+ * @author Niklas Fiekas
+ * @author stealth35
+ * @author Igor Wiedler
+ * @author Jordan Alliot
+ * @author Sergey Linnik
+ */
+class BinaryFileResponse extends Response
+{
+ protected static $trustXSendfileTypeHeader = false;
+
+ /**
+ * @var File
+ */
+ protected $file;
+ protected $offset = 0;
+ protected $maxlen = -1;
+ protected $deleteFileAfterSend = false;
+
+ /**
+ * @param \SplFileInfo|string $file The file to stream
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ * @param bool $public Files are public by default
+ * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename
+ * @param bool $autoEtag Whether the ETag header should be automatically set
+ * @param bool $autoLastModified Whether the Last-Modified header should be automatically set
+ */
+ public function __construct($file, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
+ {
+ parent::__construct(null, $status, $headers);
+
+ $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified);
+
+ if ($public) {
+ $this->setPublic();
+ }
+ }
+
+ /**
+ * @param \SplFileInfo|string $file The file to stream
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ * @param bool $public Files are public by default
+ * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename
+ * @param bool $autoEtag Whether the ETag header should be automatically set
+ * @param bool $autoLastModified Whether the Last-Modified header should be automatically set
+ *
+ * @return static
+ */
+ public static function create($file = null, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
+ {
+ return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified);
+ }
+
+ /**
+ * Sets the file to stream.
+ *
+ * @param \SplFileInfo|string $file The file to stream
+ * @param string $contentDisposition
+ * @param bool $autoEtag
+ * @param bool $autoLastModified
+ *
+ * @return $this
+ *
+ * @throws FileException
+ */
+ public function setFile($file, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
+ {
+ if (!$file instanceof File) {
+ if ($file instanceof \SplFileInfo) {
+ $file = new File($file->getPathname());
+ } else {
+ $file = new File((string) $file);
+ }
+ }
+
+ if (!$file->isReadable()) {
+ throw new FileException('File must be readable.');
+ }
+
+ $this->file = $file;
+
+ if ($autoEtag) {
+ $this->setAutoEtag();
+ }
+
+ if ($autoLastModified) {
+ $this->setAutoLastModified();
+ }
+
+ if ($contentDisposition) {
+ $this->setContentDisposition($contentDisposition);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Gets the file.
+ *
+ * @return File The file to stream
+ */
+ public function getFile()
+ {
+ return $this->file;
+ }
+
+ /**
+ * Automatically sets the Last-Modified header according the file modification date.
+ */
+ public function setAutoLastModified()
+ {
+ $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime()));
+
+ return $this;
+ }
+
+ /**
+ * Automatically sets the ETag header according to the checksum of the file.
+ */
+ public function setAutoEtag()
+ {
+ $this->setEtag(sha1_file($this->file->getPathname()));
+
+ return $this;
+ }
+
+ /**
+ * Sets the Content-Disposition header with the given filename.
+ *
+ * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT
+ * @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file
+ * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename
+ *
+ * @return $this
+ */
+ public function setContentDisposition($disposition, $filename = '', $filenameFallback = '')
+ {
+ if ('' === $filename) {
+ $filename = $this->file->getFilename();
+ }
+
+ if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) {
+ $encoding = mb_detect_encoding($filename, null, true) ?: '8bit';
+
+ for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) {
+ $char = mb_substr($filename, $i, 1, $encoding);
+
+ if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) {
+ $filenameFallback .= '_';
+ } else {
+ $filenameFallback .= $char;
+ }
+ }
+ }
+
+ $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback);
+ $this->headers->set('Content-Disposition', $dispositionHeader);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prepare(Request $request)
+ {
+ $this->headers->set('Content-Length', $this->file->getSize());
+
+ if (!$this->headers->has('Accept-Ranges')) {
+ // Only accept ranges on safe HTTP methods
+ $this->headers->set('Accept-Ranges', $request->isMethodSafe(false) ? 'bytes' : 'none');
+ }
+
+ if (!$this->headers->has('Content-Type')) {
+ $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream');
+ }
+
+ if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) {
+ $this->setProtocolVersion('1.1');
+ }
+
+ $this->ensureIEOverSSLCompatibility($request);
+
+ $this->offset = 0;
+ $this->maxlen = -1;
+
+ if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) {
+ // Use X-Sendfile, do not send any content.
+ $type = $request->headers->get('X-Sendfile-Type');
+ $path = $this->file->getRealPath();
+ // Fall back to scheme://path for stream wrapped locations.
+ if (false === $path) {
+ $path = $this->file->getPathname();
+ }
+ if ('x-accel-redirect' === strtolower($type)) {
+ // Do X-Accel-Mapping substitutions.
+ // @link http://wiki.nginx.org/X-accel#X-Accel-Redirect
+ foreach (explode(',', $request->headers->get('X-Accel-Mapping', '')) as $mapping) {
+ $mapping = explode('=', $mapping, 2);
+
+ if (2 === \count($mapping)) {
+ $pathPrefix = trim($mapping[0]);
+ $location = trim($mapping[1]);
+
+ if (substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) {
+ $path = $location.substr($path, \strlen($pathPrefix));
+ break;
+ }
+ }
+ }
+ }
+ $this->headers->set($type, $path);
+ $this->maxlen = 0;
+ } elseif ($request->headers->has('Range')) {
+ // Process the range headers.
+ if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) {
+ $range = $request->headers->get('Range');
+ $fileSize = $this->file->getSize();
+
+ list($start, $end) = explode('-', substr($range, 6), 2) + array(0);
+
+ $end = ('' === $end) ? $fileSize - 1 : (int) $end;
+
+ if ('' === $start) {
+ $start = $fileSize - $end;
+ $end = $fileSize - 1;
+ } else {
+ $start = (int) $start;
+ }
+
+ if ($start <= $end) {
+ if ($start < 0 || $end > $fileSize - 1) {
+ $this->setStatusCode(416);
+ $this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize));
+ } elseif (0 !== $start || $end !== $fileSize - 1) {
+ $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1;
+ $this->offset = $start;
+
+ $this->setStatusCode(206);
+ $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize));
+ $this->headers->set('Content-Length', $end - $start + 1);
+ }
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ private function hasValidIfRangeHeader($header)
+ {
+ if ($this->getEtag() === $header) {
+ return true;
+ }
+
+ if (null === $lastModified = $this->getLastModified()) {
+ return false;
+ }
+
+ return $lastModified->format('D, d M Y H:i:s').' GMT' === $header;
+ }
+
+ /**
+ * Sends the file.
+ *
+ * {@inheritdoc}
+ */
+ public function sendContent()
+ {
+ if (!$this->isSuccessful()) {
+ return parent::sendContent();
+ }
+
+ if (0 === $this->maxlen) {
+ return $this;
+ }
+
+ $out = fopen('php://output', 'wb');
+ $file = fopen($this->file->getPathname(), 'rb');
+
+ stream_copy_to_stream($file, $out, $this->maxlen, $this->offset);
+
+ fclose($out);
+ fclose($file);
+
+ if ($this->deleteFileAfterSend) {
+ unlink($this->file->getPathname());
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \LogicException when the content is not null
+ */
+ public function setContent($content)
+ {
+ if (null !== $content) {
+ throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return false
+ */
+ public function getContent()
+ {
+ return false;
+ }
+
+ /**
+ * Trust X-Sendfile-Type header.
+ */
+ public static function trustXSendfileTypeHeader()
+ {
+ self::$trustXSendfileTypeHeader = true;
+ }
+
+ /**
+ * If this is set to true, the file will be unlinked after the request is send
+ * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used.
+ *
+ * @param bool $shouldDelete
+ *
+ * @return $this
+ */
+ public function deleteFileAfterSend($shouldDelete)
+ {
+ $this->deleteFileAfterSend = $shouldDelete;
+
+ return $this;
+ }
+}
diff --git a/console/skel/symfony/http-foundation/CHANGELOG.md b/console/skel/symfony/http-foundation/CHANGELOG.md
new file mode 100644
index 0000000..f8b0433
--- /dev/null
+++ b/console/skel/symfony/http-foundation/CHANGELOG.md
@@ -0,0 +1,134 @@
+CHANGELOG
+=========
+
+2.8.44
+------
+
+ * [BC BREAK] Support for the IIS-only `X_ORIGINAL_URL` and `X_REWRITE_URL`
+ HTTP headers has been dropped for security reasons.
+
+2.8.0
+-----
+
+ * Finding deep items in `ParameterBag::get()` is deprecated since version 2.8 and
+ will be removed in 3.0.
+
+2.6.0
+-----
+
+ * PdoSessionHandler changes
+ - implemented different session locking strategies to prevent loss of data by concurrent access to the same session
+ - [BC BREAK] save session data in a binary column without base64_encode
+ - [BC BREAK] added lifetime column to the session table which allows to have different lifetimes for each session
+ - implemented lazy connections that are only opened when a session is used by either passing a dsn string
+ explicitly or falling back to session.save_path ini setting
+ - added a createTable method that initializes a correctly defined table depending on the database vendor
+
+2.5.0
+-----
+
+ * added `JsonResponse::setEncodingOptions()` & `JsonResponse::getEncodingOptions()` for easier manipulation
+ of the options used while encoding data to JSON format.
+
+2.4.0
+-----
+
+ * added RequestStack
+ * added Request::getEncodings()
+ * added accessors methods to session handlers
+
+2.3.0
+-----
+
+ * added support for ranges of IPs in trusted proxies
+ * `UploadedFile::isValid` now returns false if the file was not uploaded via HTTP (in a non-test mode)
+ * Improved error-handling of `\Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler`
+ to ensure the supplied PDO handler throws Exceptions on error (as the class expects). Added related test cases
+ to verify that Exceptions are properly thrown when the PDO queries fail.
+
+2.2.0
+-----
+
+ * fixed the Request::create() precedence (URI information always take precedence now)
+ * added Request::getTrustedProxies()
+ * deprecated Request::isProxyTrusted()
+ * [BC BREAK] JsonResponse does not turn a top level empty array to an object anymore, use an ArrayObject to enforce objects
+ * added a IpUtils class to check if an IP belongs to a CIDR
+ * added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method)
+ * disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to
+ enable it, and Request::getHttpMethodParameterOverride() to check if it is supported)
+ * Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3
+ * Deprecated Flashbag::count() and \Countable interface, will be removed in 2.3
+
+2.1.0
+-----
+
+ * added Request::getSchemeAndHttpHost() and Request::getUserInfo()
+ * added a fluent interface to the Response class
+ * added Request::isProxyTrusted()
+ * added JsonResponse
+ * added a getTargetUrl method to RedirectResponse
+ * added support for streamed responses
+ * made Response::prepare() method the place to enforce HTTP specification
+ * [BC BREAK] moved management of the locale from the Session class to the Request class
+ * added a generic access to the PHP built-in filter mechanism: ParameterBag::filter()
+ * made FileBinaryMimeTypeGuesser command configurable
+ * added Request::getUser() and Request::getPassword()
+ * added support for the PATCH method in Request
+ * removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3
+ * added ResponseHeaderBag::makeDisposition() (implements RFC 6266)
+ * made mimetype to extension conversion configurable
+ * [BC BREAK] Moved all session related classes and interfaces into own namespace, as
+ `Symfony\Component\HttpFoundation\Session` and renamed classes accordingly.
+ Session handlers are located in the subnamespace `Symfony\Component\HttpFoundation\Session\Handler`.
+ * SessionHandlers must implement `\SessionHandlerInterface` or extend from the
+ `Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class.
+ * Added internal storage driver proxy mechanism for forward compatibility with
+ PHP 5.4 `\SessionHandler` class.
+ * Added session handlers for custom Memcache, Memcached and Null session save handlers.
+ * [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`.
+ * [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and
+ `remove()`. Added `getBag()`, `registerBag()`. The `NativeSessionStorage` class
+ is a mediator for the session storage internals including the session handlers
+ which do the real work of participating in the internal PHP session workflow.
+ * [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit
+ and functional testing without starting real PHP sessions. Removed
+ `ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit
+ tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage`
+ for functional tests. These do not interact with global session ini
+ configuration values, session functions or `$_SESSION` superglobal. This means
+ they can be configured directly allowing multiple instances to work without
+ conflicting in the same PHP process.
+ * [BC BREAK] Removed the `close()` method from the `Session` class, as this is
+ now redundant.
+ * Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()`
+ `getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag()` instead
+ which returns a `FlashBagInterface`.
+ * `Session->clear()` now only clears session attributes as before it cleared
+ flash messages and attributes. `Session->getFlashBag()->all()` clears flashes now.
+ * Session data is now managed by `SessionBagInterface` to better encapsulate
+ session data.
+ * Refactored session attribute and flash messages system to their own
+ `SessionBagInterface` implementations.
+ * Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This
+ implementation is ESI compatible.
+ * Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire
+ behaviour of messages auto expiring after one page page load. Messages must
+ be retrieved by `get()` or `all()`.
+ * Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate
+ attributes storage behaviour from 2.0.x (default).
+ * Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for
+ namespace session attributes.
+ * Flash API can stores messages in an array so there may be multiple messages
+ per flash type. The old `Session` class API remains without BC break as it
+ will allow single messages as before.
+ * Added basic session meta-data to the session to record session create time,
+ last updated time, and the lifetime of the session cookie that was provided
+ to the client.
+ * Request::getClientIp() method doesn't take a parameter anymore but bases
+ itself on the trustProxy parameter.
+ * Added isMethod() to Request object.
+ * [BC BREAK] The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of
+ a `Request` now all return a raw value (vs a urldecoded value before). Any call
+ to one of these methods must be checked and wrapped in a `rawurldecode()` if
+ needed.
diff --git a/console/skel/symfony/http-foundation/Cookie.php b/console/skel/symfony/http-foundation/Cookie.php
new file mode 100644
index 0000000..d31f087
--- /dev/null
+++ b/console/skel/symfony/http-foundation/Cookie.php
@@ -0,0 +1,188 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Represents a cookie.
+ *
+ * @author Johannes M. Schmitt
+ */
+class Cookie
+{
+ protected $name;
+ protected $value;
+ protected $domain;
+ protected $expire;
+ protected $path;
+ protected $secure;
+ protected $httpOnly;
+
+ /**
+ * @param string $name The name of the cookie
+ * @param string $value The value of the cookie
+ * @param int|string|\DateTime|\DateTimeInterface $expire The time the cookie expires
+ * @param string $path The path on the server in which the cookie will be available on
+ * @param string $domain The domain that the cookie is available to
+ * @param bool $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client
+ * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true)
+ {
+ // from PHP source code
+ if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
+ throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
+ }
+
+ if (empty($name)) {
+ throw new \InvalidArgumentException('The cookie name cannot be empty.');
+ }
+
+ // convert expiration time to a Unix timestamp
+ if ($expire instanceof \DateTime || $expire instanceof \DateTimeInterface) {
+ $expire = $expire->format('U');
+ } elseif (!is_numeric($expire)) {
+ $expire = strtotime($expire);
+
+ if (false === $expire) {
+ throw new \InvalidArgumentException('The cookie expiration time is not valid.');
+ }
+ }
+
+ $this->name = $name;
+ $this->value = $value;
+ $this->domain = $domain;
+ $this->expire = 0 < $expire ? (int) $expire : 0;
+ $this->path = empty($path) ? '/' : $path;
+ $this->secure = (bool) $secure;
+ $this->httpOnly = (bool) $httpOnly;
+ }
+
+ /**
+ * Returns the cookie as a string.
+ *
+ * @return string The cookie
+ */
+ public function __toString()
+ {
+ $str = urlencode($this->getName()).'=';
+
+ if ('' === (string) $this->getValue()) {
+ $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001);
+ } else {
+ $str .= rawurlencode($this->getValue());
+
+ if (0 !== $this->getExpiresTime()) {
+ $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime());
+ }
+ }
+
+ if ($this->path) {
+ $str .= '; path='.$this->path;
+ }
+
+ if ($this->getDomain()) {
+ $str .= '; domain='.$this->getDomain();
+ }
+
+ if (true === $this->isSecure()) {
+ $str .= '; secure';
+ }
+
+ if (true === $this->isHttpOnly()) {
+ $str .= '; httponly';
+ }
+
+ return $str;
+ }
+
+ /**
+ * Gets the name of the cookie.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Gets the value of the cookie.
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Gets the domain that the cookie is available to.
+ *
+ * @return string
+ */
+ public function getDomain()
+ {
+ return $this->domain;
+ }
+
+ /**
+ * Gets the time the cookie expires.
+ *
+ * @return int
+ */
+ public function getExpiresTime()
+ {
+ return $this->expire;
+ }
+
+ /**
+ * Gets the path on the server in which the cookie will be available on.
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client.
+ *
+ * @return bool
+ */
+ public function isSecure()
+ {
+ return $this->secure;
+ }
+
+ /**
+ * Checks whether the cookie will be made accessible only through the HTTP protocol.
+ *
+ * @return bool
+ */
+ public function isHttpOnly()
+ {
+ return $this->httpOnly;
+ }
+
+ /**
+ * Whether this cookie is about to be cleared.
+ *
+ * @return bool
+ */
+ public function isCleared()
+ {
+ return 0 !== $this->expire && $this->expire < time();
+ }
+}
diff --git a/console/skel/symfony/http-foundation/Exception/ConflictingHeadersException.php b/console/skel/symfony/http-foundation/Exception/ConflictingHeadersException.php
new file mode 100644
index 0000000..fa5f1c7
--- /dev/null
+++ b/console/skel/symfony/http-foundation/Exception/ConflictingHeadersException.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * The HTTP request contains headers with conflicting information.
+ *
+ * This exception should trigger an HTTP 400 response in your application code.
+ *
+ * @author Magnus Nordlander
+ */
+class ConflictingHeadersException extends \RuntimeException
+{
+}
diff --git a/console/skel/symfony/http-foundation/ExpressionRequestMatcher.php b/console/skel/symfony/http-foundation/ExpressionRequestMatcher.php
new file mode 100644
index 0000000..e9c8441
--- /dev/null
+++ b/console/skel/symfony/http-foundation/ExpressionRequestMatcher.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+
+/**
+ * ExpressionRequestMatcher uses an expression to match a Request.
+ *
+ * @author Fabien Potencier
+ */
+class ExpressionRequestMatcher extends RequestMatcher
+{
+ private $language;
+ private $expression;
+
+ public function setExpression(ExpressionLanguage $language, $expression)
+ {
+ $this->language = $language;
+ $this->expression = $expression;
+ }
+
+ public function matches(Request $request)
+ {
+ if (!$this->language) {
+ throw new \LogicException('Unable to match the request as the expression language is not available.');
+ }
+
+ return $this->language->evaluate($this->expression, array(
+ 'request' => $request,
+ 'method' => $request->getMethod(),
+ 'path' => rawurldecode($request->getPathInfo()),
+ 'host' => $request->getHost(),
+ 'ip' => $request->getClientIp(),
+ 'attributes' => $request->attributes->all(),
+ )) && parent::matches($request);
+ }
+}
diff --git a/console/skel/symfony/http-foundation/File/Exception/AccessDeniedException.php b/console/skel/symfony/http-foundation/File/Exception/AccessDeniedException.php
new file mode 100644
index 0000000..3b8e41d
--- /dev/null
+++ b/console/skel/symfony/http-foundation/File/Exception/AccessDeniedException.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when the access on a file was denied.
+ *
+ * @author Bernhard Schussek
+ */
+class AccessDeniedException extends FileException
+{
+ /**
+ * @param string $path The path to the accessed file
+ */
+ public function __construct($path)
+ {
+ parent::__construct(sprintf('The file %s could not be accessed', $path));
+ }
+}
diff --git a/console/skel/symfony/http-foundation/File/Exception/FileException.php b/console/skel/symfony/http-foundation/File/Exception/FileException.php
new file mode 100644
index 0000000..fad5133
--- /dev/null
+++ b/console/skel/symfony/http-foundation/File/Exception/FileException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an error occurred in the component File.
+ *
+ * @author Bernhard Schussek
+ */
+class FileException extends \RuntimeException
+{
+}
diff --git a/console/skel/symfony/http-foundation/File/Exception/FileNotFoundException.php b/console/skel/symfony/http-foundation/File/Exception/FileNotFoundException.php
new file mode 100644
index 0000000..bfcc37e
--- /dev/null
+++ b/console/skel/symfony/http-foundation/File/Exception/FileNotFoundException.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when a file was not found.
+ *
+ * @author Bernhard Schussek
+ */
+class FileNotFoundException extends FileException
+{
+ /**
+ * @param string $path The path to the file that was not found
+ */
+ public function __construct($path)
+ {
+ parent::__construct(sprintf('The file "%s" does not exist', $path));
+ }
+}
diff --git a/console/skel/symfony/http-foundation/File/Exception/UnexpectedTypeException.php b/console/skel/symfony/http-foundation/File/Exception/UnexpectedTypeException.php
new file mode 100644
index 0000000..62005d3
--- /dev/null
+++ b/console/skel/symfony/http-foundation/File/Exception/UnexpectedTypeException.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+class UnexpectedTypeException extends FileException
+{
+ public function __construct($value, $expectedType)
+ {
+ parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, \is_object($value) ? \get_class($value) : \gettype($value)));
+ }
+}
diff --git a/console/skel/symfony/http-foundation/File/Exception/UploadException.php b/console/skel/symfony/http-foundation/File/Exception/UploadException.php
new file mode 100644
index 0000000..7074e76
--- /dev/null
+++ b/console/skel/symfony/http-foundation/File/Exception/UploadException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an error occurred during file upload.
+ *
+ * @author Bernhard Schussek
+ */
+class UploadException extends FileException
+{
+}
diff --git a/console/skel/symfony/http-foundation/File/File.php b/console/skel/symfony/http-foundation/File/File.php
new file mode 100644
index 0000000..3422058
--- /dev/null
+++ b/console/skel/symfony/http-foundation/File/File.php
@@ -0,0 +1,138 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser;
+use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
+
+/**
+ * A file in the file system.
+ *
+ * @author Bernhard Schussek
+ */
+class File extends \SplFileInfo
+{
+ /**
+ * Constructs a new file from the given path.
+ *
+ * @param string $path The path to the file
+ * @param bool $checkPath Whether to check the path or not
+ *
+ * @throws FileNotFoundException If the given path is not a file
+ */
+ public function __construct($path, $checkPath = true)
+ {
+ if ($checkPath && !is_file($path)) {
+ throw new FileNotFoundException($path);
+ }
+
+ parent::__construct($path);
+ }
+
+ /**
+ * Returns the extension based on the mime type.
+ *
+ * If the mime type is unknown, returns null.
+ *
+ * This method uses the mime type as guessed by getMimeType()
+ * to guess the file extension.
+ *
+ * @return string|null The guessed extension or null if it cannot be guessed
+ *
+ * @see ExtensionGuesser
+ * @see getMimeType()
+ */
+ public function guessExtension()
+ {
+ $type = $this->getMimeType();
+ $guesser = ExtensionGuesser::getInstance();
+
+ return $guesser->guess($type);
+ }
+
+ /**
+ * Returns the mime type of the file.
+ *
+ * The mime type is guessed using a MimeTypeGuesser instance, which uses finfo(),
+ * mime_content_type() and the system binary "file" (in this order), depending on
+ * which of those are available.
+ *
+ * @return string|null The guessed mime type (e.g. "application/pdf")
+ *
+ * @see MimeTypeGuesser
+ */
+ public function getMimeType()
+ {
+ $guesser = MimeTypeGuesser::getInstance();
+
+ return $guesser->guess($this->getPathname());
+ }
+
+ /**
+ * Moves the file to a new location.
+ *
+ * @param string $directory The destination folder
+ * @param string $name The new file name
+ *
+ * @return self A File object representing the new file
+ *
+ * @throws FileException if the target file could not be created
+ */
+ public function move($directory, $name = null)
+ {
+ $target = $this->getTargetFile($directory, $name);
+
+ set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
+ $renamed = rename($this->getPathname(), $target);
+ restore_error_handler();
+ if (!$renamed) {
+ throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
+ }
+
+ @chmod($target, 0666 & ~umask());
+
+ return $target;
+ }
+
+ protected function getTargetFile($directory, $name = null)
+ {
+ if (!is_dir($directory)) {
+ if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
+ throw new FileException(sprintf('Unable to create the "%s" directory', $directory));
+ }
+ } elseif (!is_writable($directory)) {
+ throw new FileException(sprintf('Unable to write in the "%s" directory', $directory));
+ }
+
+ $target = rtrim($directory, '/\\').\DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name));
+
+ return new self($target, false);
+ }
+
+ /**
+ * Returns locale independent base name of the given path.
+ *
+ * @param string $name The new file name
+ *
+ * @return string containing
+ */
+ protected function getName($name)
+ {
+ $originalName = str_replace('\\', '/', $name);
+ $pos = strrpos($originalName, '/');
+ $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1);
+
+ return $originalName;
+ }
+}
diff --git a/console/skel/symfony/http-foundation/File/MimeType/ExtensionGuesser.php b/console/skel/symfony/http-foundation/File/MimeType/ExtensionGuesser.php
new file mode 100644
index 0000000..263fb32
--- /dev/null
+++ b/console/skel/symfony/http-foundation/File/MimeType/ExtensionGuesser.php
@@ -0,0 +1,94 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+/**
+ * A singleton mime type to file extension guesser.
+ *
+ * A default guesser is provided.
+ * You can register custom guessers by calling the register()
+ * method on the singleton instance:
+ *
+ * $guesser = ExtensionGuesser::getInstance();
+ * $guesser->register(new MyCustomExtensionGuesser());
+ *
+ * The last registered guesser is preferred over previously registered ones.
+ */
+class ExtensionGuesser implements ExtensionGuesserInterface
+{
+ /**
+ * The singleton instance.
+ *
+ * @var ExtensionGuesser
+ */
+ private static $instance = null;
+
+ /**
+ * All registered ExtensionGuesserInterface instances.
+ *
+ * @var array
+ */
+ protected $guessers = array();
+
+ /**
+ * Returns the singleton instance.
+ *
+ * @return self
+ */
+ public static function getInstance()
+ {
+ if (null === self::$instance) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Registers all natively provided extension guessers.
+ */
+ private function __construct()
+ {
+ $this->register(new MimeTypeExtensionGuesser());
+ }
+
+ /**
+ * Registers a new extension guesser.
+ *
+ * When guessing, this guesser is preferred over previously registered ones.
+ */
+ public function register(ExtensionGuesserInterface $guesser)
+ {
+ array_unshift($this->guessers, $guesser);
+ }
+
+ /**
+ * Tries to guess the extension.
+ *
+ * The mime type is passed to each registered mime type guesser in reverse order
+ * of their registration (last registered is queried first). Once a guesser
+ * returns a value that is not NULL, this method terminates and returns the
+ * value.
+ *
+ * @param string $mimeType The mime type
+ *
+ * @return string The guessed extension or NULL, if none could be guessed
+ */
+ public function guess($mimeType)
+ {
+ foreach ($this->guessers as $guesser) {
+ if (null !== $extension = $guesser->guess($mimeType)) {
+ return $extension;
+ }
+ }
+ }
+}
diff --git a/console/skel/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php b/console/skel/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php
new file mode 100644
index 0000000..d19a0e5
--- /dev/null
+++ b/console/skel/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+/**
+ * Guesses the file extension corresponding to a given mime type.
+ */
+interface ExtensionGuesserInterface
+{
+ /**
+ * Makes a best guess for a file extension, given a mime type.
+ *
+ * @param string $mimeType The mime type
+ *
+ * @return string The guessed extension or NULL, if none could be guessed
+ */
+ public function guess($mimeType);
+}
diff --git a/console/skel/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php b/console/skel/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php
new file mode 100644
index 0000000..a3a3601
--- /dev/null
+++ b/console/skel/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php
@@ -0,0 +1,99 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+
+/**
+ * Guesses the mime type with the binary "file" (only available on *nix).
+ *
+ * @author Bernhard Schussek
+ */
+class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
+{
+ private $cmd;
+
+ /**
+ * The $cmd pattern must contain a "%s" string that will be replaced
+ * with the file name to guess.
+ *
+ * The command output must start with the mime type of the file.
+ *
+ * @param string $cmd The command to run to get the mime type of a file
+ */
+ public function __construct($cmd = 'file -b --mime -- %s 2>/dev/null')
+ {
+ $this->cmd = $cmd;
+ }
+
+ /**
+ * Returns whether this guesser is supported on the current OS.
+ *
+ * @return bool
+ */
+ public static function isSupported()
+ {
+ static $supported = null;
+
+ if (null !== $supported) {
+ return $supported;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('passthru') || !\function_exists('escapeshellarg')) {
+ return $supported = false;
+ }
+
+ ob_start();
+ passthru('command -v file', $exitStatus);
+ $binPath = trim(ob_get_clean());
+
+ return $supported = 0 === $exitStatus && '' !== $binPath;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function guess($path)
+ {
+ if (!is_file($path)) {
+ throw new FileNotFoundException($path);
+ }
+
+ if (!is_readable($path)) {
+ throw new AccessDeniedException($path);
+ }
+
+ if (!self::isSupported()) {
+ return;
+ }
+
+ ob_start();
+
+ // need to use --mime instead of -i. see #6641
+ passthru(sprintf($this->cmd, escapeshellarg((0 === strpos($path, '-') ? './' : '').$path)), $return);
+ if ($return > 0) {
+ ob_end_clean();
+
+ return;
+ }
+
+ $type = trim(ob_get_clean());
+
+ if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) {
+ // it's not a type, but an error message
+ return;
+ }
+
+ return $match[1];
+ }
+}
diff --git a/console/skel/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php b/console/skel/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php
new file mode 100644
index 0000000..bf1ee9f
--- /dev/null
+++ b/console/skel/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+
+/**
+ * Guesses the mime type using the PECL extension FileInfo.
+ *
+ * @author Bernhard Schussek
+ */
+class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
+{
+ private $magicFile;
+
+ /**
+ * @param string $magicFile A magic file to use with the finfo instance
+ *
+ * @see http://www.php.net/manual/en/function.finfo-open.php
+ */
+ public function __construct($magicFile = null)
+ {
+ $this->magicFile = $magicFile;
+ }
+
+ /**
+ * Returns whether this guesser is supported on the current OS/PHP setup.
+ *
+ * @return bool
+ */
+ public static function isSupported()
+ {
+ return \function_exists('finfo_open');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function guess($path)
+ {
+ if (!is_file($path)) {
+ throw new FileNotFoundException($path);
+ }
+
+ if (!is_readable($path)) {
+ throw new AccessDeniedException($path);
+ }
+
+ if (!self::isSupported()) {
+ return;
+ }
+
+ if (!$finfo = new \finfo(FILEINFO_MIME_TYPE, $this->magicFile)) {
+ return;
+ }
+
+ return $finfo->file($path);
+ }
+}
diff --git a/console/skel/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php b/console/skel/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php
new file mode 100644
index 0000000..36271af
--- /dev/null
+++ b/console/skel/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php
@@ -0,0 +1,807 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+/**
+ * Provides a best-guess mapping of mime type to file extension.
+ */
+class MimeTypeExtensionGuesser implements ExtensionGuesserInterface
+{
+ /**
+ * A map of mime types and their default extensions.
+ *
+ * This list has been placed under the public domain by the Apache HTTPD project.
+ * This list has been updated from upstream on 2013-04-23.
+ *
+ * @see http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
+ */
+ protected $defaultExtensions = array(
+ 'application/andrew-inset' => 'ez',
+ 'application/applixware' => 'aw',
+ 'application/atom+xml' => 'atom',
+ 'application/atomcat+xml' => 'atomcat',
+ 'application/atomsvc+xml' => 'atomsvc',
+ 'application/ccxml+xml' => 'ccxml',
+ 'application/cdmi-capability' => 'cdmia',
+ 'application/cdmi-container' => 'cdmic',
+ 'application/cdmi-domain' => 'cdmid',
+ 'application/cdmi-object' => 'cdmio',
+ 'application/cdmi-queue' => 'cdmiq',
+ 'application/cu-seeme' => 'cu',
+ 'application/davmount+xml' => 'davmount',
+ 'application/docbook+xml' => 'dbk',
+ 'application/dssc+der' => 'dssc',
+ 'application/dssc+xml' => 'xdssc',
+ 'application/ecmascript' => 'ecma',
+ 'application/emma+xml' => 'emma',
+ 'application/epub+zip' => 'epub',
+ 'application/exi' => 'exi',
+ 'application/font-tdpfr' => 'pfr',
+ 'application/gml+xml' => 'gml',
+ 'application/gpx+xml' => 'gpx',
+ 'application/gxf' => 'gxf',
+ 'application/hyperstudio' => 'stk',
+ 'application/inkml+xml' => 'ink',
+ 'application/ipfix' => 'ipfix',
+ 'application/java-archive' => 'jar',
+ 'application/java-serialized-object' => 'ser',
+ 'application/java-vm' => 'class',
+ 'application/javascript' => 'js',
+ 'application/json' => 'json',
+ 'application/jsonml+json' => 'jsonml',
+ 'application/lost+xml' => 'lostxml',
+ 'application/mac-binhex40' => 'hqx',
+ 'application/mac-compactpro' => 'cpt',
+ 'application/mads+xml' => 'mads',
+ 'application/marc' => 'mrc',
+ 'application/marcxml+xml' => 'mrcx',
+ 'application/mathematica' => 'ma',
+ 'application/mathml+xml' => 'mathml',
+ 'application/mbox' => 'mbox',
+ 'application/mediaservercontrol+xml' => 'mscml',
+ 'application/metalink+xml' => 'metalink',
+ 'application/metalink4+xml' => 'meta4',
+ 'application/mets+xml' => 'mets',
+ 'application/mods+xml' => 'mods',
+ 'application/mp21' => 'm21',
+ 'application/mp4' => 'mp4s',
+ 'application/msword' => 'doc',
+ 'application/mxf' => 'mxf',
+ 'application/octet-stream' => 'bin',
+ 'application/oda' => 'oda',
+ 'application/oebps-package+xml' => 'opf',
+ 'application/ogg' => 'ogx',
+ 'application/omdoc+xml' => 'omdoc',
+ 'application/onenote' => 'onetoc',
+ 'application/oxps' => 'oxps',
+ 'application/patch-ops-error+xml' => 'xer',
+ 'application/pdf' => 'pdf',
+ 'application/pgp-encrypted' => 'pgp',
+ 'application/pgp-signature' => 'asc',
+ 'application/pics-rules' => 'prf',
+ 'application/pkcs10' => 'p10',
+ 'application/pkcs7-mime' => 'p7m',
+ 'application/pkcs7-signature' => 'p7s',
+ 'application/pkcs8' => 'p8',
+ 'application/pkix-attr-cert' => 'ac',
+ 'application/pkix-cert' => 'cer',
+ 'application/pkix-crl' => 'crl',
+ 'application/pkix-pkipath' => 'pkipath',
+ 'application/pkixcmp' => 'pki',
+ 'application/pls+xml' => 'pls',
+ 'application/postscript' => 'ai',
+ 'application/prs.cww' => 'cww',
+ 'application/pskc+xml' => 'pskcxml',
+ 'application/rdf+xml' => 'rdf',
+ 'application/reginfo+xml' => 'rif',
+ 'application/relax-ng-compact-syntax' => 'rnc',
+ 'application/resource-lists+xml' => 'rl',
+ 'application/resource-lists-diff+xml' => 'rld',
+ 'application/rls-services+xml' => 'rs',
+ 'application/rpki-ghostbusters' => 'gbr',
+ 'application/rpki-manifest' => 'mft',
+ 'application/rpki-roa' => 'roa',
+ 'application/rsd+xml' => 'rsd',
+ 'application/rss+xml' => 'rss',
+ 'application/rtf' => 'rtf',
+ 'application/sbml+xml' => 'sbml',
+ 'application/scvp-cv-request' => 'scq',
+ 'application/scvp-cv-response' => 'scs',
+ 'application/scvp-vp-request' => 'spq',
+ 'application/scvp-vp-response' => 'spp',
+ 'application/sdp' => 'sdp',
+ 'application/set-payment-initiation' => 'setpay',
+ 'application/set-registration-initiation' => 'setreg',
+ 'application/shf+xml' => 'shf',
+ 'application/smil+xml' => 'smi',
+ 'application/sparql-query' => 'rq',
+ 'application/sparql-results+xml' => 'srx',
+ 'application/srgs' => 'gram',
+ 'application/srgs+xml' => 'grxml',
+ 'application/sru+xml' => 'sru',
+ 'application/ssdl+xml' => 'ssdl',
+ 'application/ssml+xml' => 'ssml',
+ 'application/tei+xml' => 'tei',
+ 'application/thraud+xml' => 'tfi',
+ 'application/timestamped-data' => 'tsd',
+ 'application/vnd.3gpp.pic-bw-large' => 'plb',
+ 'application/vnd.3gpp.pic-bw-small' => 'psb',
+ 'application/vnd.3gpp.pic-bw-var' => 'pvb',
+ 'application/vnd.3gpp2.tcap' => 'tcap',
+ 'application/vnd.3m.post-it-notes' => 'pwn',
+ 'application/vnd.accpac.simply.aso' => 'aso',
+ 'application/vnd.accpac.simply.imp' => 'imp',
+ 'application/vnd.acucobol' => 'acu',
+ 'application/vnd.acucorp' => 'atc',
+ 'application/vnd.adobe.air-application-installer-package+zip' => 'air',
+ 'application/vnd.adobe.formscentral.fcdt' => 'fcdt',
+ 'application/vnd.adobe.fxp' => 'fxp',
+ 'application/vnd.adobe.xdp+xml' => 'xdp',
+ 'application/vnd.adobe.xfdf' => 'xfdf',
+ 'application/vnd.ahead.space' => 'ahead',
+ 'application/vnd.airzip.filesecure.azf' => 'azf',
+ 'application/vnd.airzip.filesecure.azs' => 'azs',
+ 'application/vnd.amazon.ebook' => 'azw',
+ 'application/vnd.americandynamics.acc' => 'acc',
+ 'application/vnd.amiga.ami' => 'ami',
+ 'application/vnd.android.package-archive' => 'apk',
+ 'application/vnd.anser-web-certificate-issue-initiation' => 'cii',
+ 'application/vnd.anser-web-funds-transfer-initiation' => 'fti',
+ 'application/vnd.antix.game-component' => 'atx',
+ 'application/vnd.apple.installer+xml' => 'mpkg',
+ 'application/vnd.apple.mpegurl' => 'm3u8',
+ 'application/vnd.aristanetworks.swi' => 'swi',
+ 'application/vnd.astraea-software.iota' => 'iota',
+ 'application/vnd.audiograph' => 'aep',
+ 'application/vnd.blueice.multipass' => 'mpm',
+ 'application/vnd.bmi' => 'bmi',
+ 'application/vnd.businessobjects' => 'rep',
+ 'application/vnd.chemdraw+xml' => 'cdxml',
+ 'application/vnd.chipnuts.karaoke-mmd' => 'mmd',
+ 'application/vnd.cinderella' => 'cdy',
+ 'application/vnd.claymore' => 'cla',
+ 'application/vnd.cloanto.rp9' => 'rp9',
+ 'application/vnd.clonk.c4group' => 'c4g',
+ 'application/vnd.cluetrust.cartomobile-config' => 'c11amc',
+ 'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz',
+ 'application/vnd.commonspace' => 'csp',
+ 'application/vnd.contact.cmsg' => 'cdbcmsg',
+ 'application/vnd.cosmocaller' => 'cmc',
+ 'application/vnd.crick.clicker' => 'clkx',
+ 'application/vnd.crick.clicker.keyboard' => 'clkk',
+ 'application/vnd.crick.clicker.palette' => 'clkp',
+ 'application/vnd.crick.clicker.template' => 'clkt',
+ 'application/vnd.crick.clicker.wordbank' => 'clkw',
+ 'application/vnd.criticaltools.wbs+xml' => 'wbs',
+ 'application/vnd.ctc-posml' => 'pml',
+ 'application/vnd.cups-ppd' => 'ppd',
+ 'application/vnd.curl.car' => 'car',
+ 'application/vnd.curl.pcurl' => 'pcurl',
+ 'application/vnd.dart' => 'dart',
+ 'application/vnd.data-vision.rdz' => 'rdz',
+ 'application/vnd.dece.data' => 'uvf',
+ 'application/vnd.dece.ttml+xml' => 'uvt',
+ 'application/vnd.dece.unspecified' => 'uvx',
+ 'application/vnd.dece.zip' => 'uvz',
+ 'application/vnd.denovo.fcselayout-link' => 'fe_launch',
+ 'application/vnd.dna' => 'dna',
+ 'application/vnd.dolby.mlp' => 'mlp',
+ 'application/vnd.dpgraph' => 'dpg',
+ 'application/vnd.dreamfactory' => 'dfac',
+ 'application/vnd.ds-keypoint' => 'kpxx',
+ 'application/vnd.dvb.ait' => 'ait',
+ 'application/vnd.dvb.service' => 'svc',
+ 'application/vnd.dynageo' => 'geo',
+ 'application/vnd.ecowin.chart' => 'mag',
+ 'application/vnd.enliven' => 'nml',
+ 'application/vnd.epson.esf' => 'esf',
+ 'application/vnd.epson.msf' => 'msf',
+ 'application/vnd.epson.quickanime' => 'qam',
+ 'application/vnd.epson.salt' => 'slt',
+ 'application/vnd.epson.ssf' => 'ssf',
+ 'application/vnd.eszigno3+xml' => 'es3',
+ 'application/vnd.ezpix-album' => 'ez2',
+ 'application/vnd.ezpix-package' => 'ez3',
+ 'application/vnd.fdf' => 'fdf',
+ 'application/vnd.fdsn.mseed' => 'mseed',
+ 'application/vnd.fdsn.seed' => 'seed',
+ 'application/vnd.flographit' => 'gph',
+ 'application/vnd.fluxtime.clip' => 'ftc',
+ 'application/vnd.framemaker' => 'fm',
+ 'application/vnd.frogans.fnc' => 'fnc',
+ 'application/vnd.frogans.ltf' => 'ltf',
+ 'application/vnd.fsc.weblaunch' => 'fsc',
+ 'application/vnd.fujitsu.oasys' => 'oas',
+ 'application/vnd.fujitsu.oasys2' => 'oa2',
+ 'application/vnd.fujitsu.oasys3' => 'oa3',
+ 'application/vnd.fujitsu.oasysgp' => 'fg5',
+ 'application/vnd.fujitsu.oasysprs' => 'bh2',
+ 'application/vnd.fujixerox.ddd' => 'ddd',
+ 'application/vnd.fujixerox.docuworks' => 'xdw',
+ 'application/vnd.fujixerox.docuworks.binder' => 'xbd',
+ 'application/vnd.fuzzysheet' => 'fzs',
+ 'application/vnd.genomatix.tuxedo' => 'txd',
+ 'application/vnd.geogebra.file' => 'ggb',
+ 'application/vnd.geogebra.tool' => 'ggt',
+ 'application/vnd.geometry-explorer' => 'gex',
+ 'application/vnd.geonext' => 'gxt',
+ 'application/vnd.geoplan' => 'g2w',
+ 'application/vnd.geospace' => 'g3w',
+ 'application/vnd.gmx' => 'gmx',
+ 'application/vnd.google-earth.kml+xml' => 'kml',
+ 'application/vnd.google-earth.kmz' => 'kmz',
+ 'application/vnd.grafeq' => 'gqf',
+ 'application/vnd.groove-account' => 'gac',
+ 'application/vnd.groove-help' => 'ghf',
+ 'application/vnd.groove-identity-message' => 'gim',
+ 'application/vnd.groove-injector' => 'grv',
+ 'application/vnd.groove-tool-message' => 'gtm',
+ 'application/vnd.groove-tool-template' => 'tpl',
+ 'application/vnd.groove-vcard' => 'vcg',
+ 'application/vnd.hal+xml' => 'hal',
+ 'application/vnd.handheld-entertainment+xml' => 'zmm',
+ 'application/vnd.hbci' => 'hbci',
+ 'application/vnd.hhe.lesson-player' => 'les',
+ 'application/vnd.hp-hpgl' => 'hpgl',
+ 'application/vnd.hp-hpid' => 'hpid',
+ 'application/vnd.hp-hps' => 'hps',
+ 'application/vnd.hp-jlyt' => 'jlt',
+ 'application/vnd.hp-pcl' => 'pcl',
+ 'application/vnd.hp-pclxl' => 'pclxl',
+ 'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx',
+ 'application/vnd.ibm.minipay' => 'mpy',
+ 'application/vnd.ibm.modcap' => 'afp',
+ 'application/vnd.ibm.rights-management' => 'irm',
+ 'application/vnd.ibm.secure-container' => 'sc',
+ 'application/vnd.iccprofile' => 'icc',
+ 'application/vnd.igloader' => 'igl',
+ 'application/vnd.immervision-ivp' => 'ivp',
+ 'application/vnd.immervision-ivu' => 'ivu',
+ 'application/vnd.insors.igm' => 'igm',
+ 'application/vnd.intercon.formnet' => 'xpw',
+ 'application/vnd.intergeo' => 'i2g',
+ 'application/vnd.intu.qbo' => 'qbo',
+ 'application/vnd.intu.qfx' => 'qfx',
+ 'application/vnd.ipunplugged.rcprofile' => 'rcprofile',
+ 'application/vnd.irepository.package+xml' => 'irp',
+ 'application/vnd.is-xpr' => 'xpr',
+ 'application/vnd.isac.fcs' => 'fcs',
+ 'application/vnd.jam' => 'jam',
+ 'application/vnd.jcp.javame.midlet-rms' => 'rms',
+ 'application/vnd.jisp' => 'jisp',
+ 'application/vnd.joost.joda-archive' => 'joda',
+ 'application/vnd.kahootz' => 'ktz',
+ 'application/vnd.kde.karbon' => 'karbon',
+ 'application/vnd.kde.kchart' => 'chrt',
+ 'application/vnd.kde.kformula' => 'kfo',
+ 'application/vnd.kde.kivio' => 'flw',
+ 'application/vnd.kde.kontour' => 'kon',
+ 'application/vnd.kde.kpresenter' => 'kpr',
+ 'application/vnd.kde.kspread' => 'ksp',
+ 'application/vnd.kde.kword' => 'kwd',
+ 'application/vnd.kenameaapp' => 'htke',
+ 'application/vnd.kidspiration' => 'kia',
+ 'application/vnd.kinar' => 'kne',
+ 'application/vnd.koan' => 'skp',
+ 'application/vnd.kodak-descriptor' => 'sse',
+ 'application/vnd.las.las+xml' => 'lasxml',
+ 'application/vnd.llamagraphics.life-balance.desktop' => 'lbd',
+ 'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe',
+ 'application/vnd.lotus-1-2-3' => '123',
+ 'application/vnd.lotus-approach' => 'apr',
+ 'application/vnd.lotus-freelance' => 'pre',
+ 'application/vnd.lotus-notes' => 'nsf',
+ 'application/vnd.lotus-organizer' => 'org',
+ 'application/vnd.lotus-screencam' => 'scm',
+ 'application/vnd.lotus-wordpro' => 'lwp',
+ 'application/vnd.macports.portpkg' => 'portpkg',
+ 'application/vnd.mcd' => 'mcd',
+ 'application/vnd.medcalcdata' => 'mc1',
+ 'application/vnd.mediastation.cdkey' => 'cdkey',
+ 'application/vnd.mfer' => 'mwf',
+ 'application/vnd.mfmp' => 'mfm',
+ 'application/vnd.micrografx.flo' => 'flo',
+ 'application/vnd.micrografx.igx' => 'igx',
+ 'application/vnd.mif' => 'mif',
+ 'application/vnd.mobius.daf' => 'daf',
+ 'application/vnd.mobius.dis' => 'dis',
+ 'application/vnd.mobius.mbk' => 'mbk',
+ 'application/vnd.mobius.mqy' => 'mqy',
+ 'application/vnd.mobius.msl' => 'msl',
+ 'application/vnd.mobius.plc' => 'plc',
+ 'application/vnd.mobius.txf' => 'txf',
+ 'application/vnd.mophun.application' => 'mpn',
+ 'application/vnd.mophun.certificate' => 'mpc',
+ 'application/vnd.mozilla.xul+xml' => 'xul',
+ 'application/vnd.ms-artgalry' => 'cil',
+ 'application/vnd.ms-cab-compressed' => 'cab',
+ 'application/vnd.ms-excel' => 'xls',
+ 'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam',
+ 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb',
+ 'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm',
+ 'application/vnd.ms-excel.template.macroenabled.12' => 'xltm',
+ 'application/vnd.ms-fontobject' => 'eot',
+ 'application/vnd.ms-htmlhelp' => 'chm',
+ 'application/vnd.ms-ims' => 'ims',
+ 'application/vnd.ms-lrm' => 'lrm',
+ 'application/vnd.ms-officetheme' => 'thmx',
+ 'application/vnd.ms-pki.seccat' => 'cat',
+ 'application/vnd.ms-pki.stl' => 'stl',
+ 'application/vnd.ms-powerpoint' => 'ppt',
+ 'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam',
+ 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm',
+ 'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm',
+ 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm',
+ 'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm',
+ 'application/vnd.ms-project' => 'mpp',
+ 'application/vnd.ms-word.document.macroenabled.12' => 'docm',
+ 'application/vnd.ms-word.template.macroenabled.12' => 'dotm',
+ 'application/vnd.ms-works' => 'wps',
+ 'application/vnd.ms-wpl' => 'wpl',
+ 'application/vnd.ms-xpsdocument' => 'xps',
+ 'application/vnd.mseq' => 'mseq',
+ 'application/vnd.musician' => 'mus',
+ 'application/vnd.muvee.style' => 'msty',
+ 'application/vnd.mynfc' => 'taglet',
+ 'application/vnd.neurolanguage.nlu' => 'nlu',
+ 'application/vnd.nitf' => 'ntf',
+ 'application/vnd.noblenet-directory' => 'nnd',
+ 'application/vnd.noblenet-sealer' => 'nns',
+ 'application/vnd.noblenet-web' => 'nnw',
+ 'application/vnd.nokia.n-gage.data' => 'ngdat',
+ 'application/vnd.nokia.n-gage.symbian.install' => 'n-gage',
+ 'application/vnd.nokia.radio-preset' => 'rpst',
+ 'application/vnd.nokia.radio-presets' => 'rpss',
+ 'application/vnd.novadigm.edm' => 'edm',
+ 'application/vnd.novadigm.edx' => 'edx',
+ 'application/vnd.novadigm.ext' => 'ext',
+ 'application/vnd.oasis.opendocument.chart' => 'odc',
+ 'application/vnd.oasis.opendocument.chart-template' => 'otc',
+ 'application/vnd.oasis.opendocument.database' => 'odb',
+ 'application/vnd.oasis.opendocument.formula' => 'odf',
+ 'application/vnd.oasis.opendocument.formula-template' => 'odft',
+ 'application/vnd.oasis.opendocument.graphics' => 'odg',
+ 'application/vnd.oasis.opendocument.graphics-template' => 'otg',
+ 'application/vnd.oasis.opendocument.image' => 'odi',
+ 'application/vnd.oasis.opendocument.image-template' => 'oti',
+ 'application/vnd.oasis.opendocument.presentation' => 'odp',
+ 'application/vnd.oasis.opendocument.presentation-template' => 'otp',
+ 'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
+ 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots',
+ 'application/vnd.oasis.opendocument.text' => 'odt',
+ 'application/vnd.oasis.opendocument.text-master' => 'odm',
+ 'application/vnd.oasis.opendocument.text-template' => 'ott',
+ 'application/vnd.oasis.opendocument.text-web' => 'oth',
+ 'application/vnd.olpc-sugar' => 'xo',
+ 'application/vnd.oma.dd2+xml' => 'dd2',
+ 'application/vnd.openofficeorg.extension' => 'oxt',
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx',
+ 'application/vnd.osgeo.mapguide.package' => 'mgp',
+ 'application/vnd.osgi.dp' => 'dp',
+ 'application/vnd.osgi.subsystem' => 'esa',
+ 'application/vnd.palm' => 'pdb',
+ 'application/vnd.pawaafile' => 'paw',
+ 'application/vnd.pg.format' => 'str',
+ 'application/vnd.pg.osasli' => 'ei6',
+ 'application/vnd.picsel' => 'efif',
+ 'application/vnd.pmi.widget' => 'wg',
+ 'application/vnd.pocketlearn' => 'plf',
+ 'application/vnd.powerbuilder6' => 'pbd',
+ 'application/vnd.previewsystems.box' => 'box',
+ 'application/vnd.proteus.magazine' => 'mgz',
+ 'application/vnd.publishare-delta-tree' => 'qps',
+ 'application/vnd.pvi.ptid1' => 'ptid',
+ 'application/vnd.quark.quarkxpress' => 'qxd',
+ 'application/vnd.realvnc.bed' => 'bed',
+ 'application/vnd.recordare.musicxml' => 'mxl',
+ 'application/vnd.recordare.musicxml+xml' => 'musicxml',
+ 'application/vnd.rig.cryptonote' => 'cryptonote',
+ 'application/vnd.rim.cod' => 'cod',
+ 'application/vnd.rn-realmedia' => 'rm',
+ 'application/vnd.rn-realmedia-vbr' => 'rmvb',
+ 'application/vnd.route66.link66+xml' => 'link66',
+ 'application/vnd.sailingtracker.track' => 'st',
+ 'application/vnd.seemail' => 'see',
+ 'application/vnd.sema' => 'sema',
+ 'application/vnd.semd' => 'semd',
+ 'application/vnd.semf' => 'semf',
+ 'application/vnd.shana.informed.formdata' => 'ifm',
+ 'application/vnd.shana.informed.formtemplate' => 'itp',
+ 'application/vnd.shana.informed.interchange' => 'iif',
+ 'application/vnd.shana.informed.package' => 'ipk',
+ 'application/vnd.simtech-mindmapper' => 'twd',
+ 'application/vnd.smaf' => 'mmf',
+ 'application/vnd.smart.teacher' => 'teacher',
+ 'application/vnd.solent.sdkm+xml' => 'sdkm',
+ 'application/vnd.spotfire.dxp' => 'dxp',
+ 'application/vnd.spotfire.sfs' => 'sfs',
+ 'application/vnd.stardivision.calc' => 'sdc',
+ 'application/vnd.stardivision.draw' => 'sda',
+ 'application/vnd.stardivision.impress' => 'sdd',
+ 'application/vnd.stardivision.math' => 'smf',
+ 'application/vnd.stardivision.writer' => 'sdw',
+ 'application/vnd.stardivision.writer-global' => 'sgl',
+ 'application/vnd.stepmania.package' => 'smzip',
+ 'application/vnd.stepmania.stepchart' => 'sm',
+ 'application/vnd.sun.xml.calc' => 'sxc',
+ 'application/vnd.sun.xml.calc.template' => 'stc',
+ 'application/vnd.sun.xml.draw' => 'sxd',
+ 'application/vnd.sun.xml.draw.template' => 'std',
+ 'application/vnd.sun.xml.impress' => 'sxi',
+ 'application/vnd.sun.xml.impress.template' => 'sti',
+ 'application/vnd.sun.xml.math' => 'sxm',
+ 'application/vnd.sun.xml.writer' => 'sxw',
+ 'application/vnd.sun.xml.writer.global' => 'sxg',
+ 'application/vnd.sun.xml.writer.template' => 'stw',
+ 'application/vnd.sus-calendar' => 'sus',
+ 'application/vnd.svd' => 'svd',
+ 'application/vnd.symbian.install' => 'sis',
+ 'application/vnd.syncml+xml' => 'xsm',
+ 'application/vnd.syncml.dm+wbxml' => 'bdm',
+ 'application/vnd.syncml.dm+xml' => 'xdm',
+ 'application/vnd.tao.intent-module-archive' => 'tao',
+ 'application/vnd.tcpdump.pcap' => 'pcap',
+ 'application/vnd.tmobile-livetv' => 'tmo',
+ 'application/vnd.trid.tpt' => 'tpt',
+ 'application/vnd.triscape.mxs' => 'mxs',
+ 'application/vnd.trueapp' => 'tra',
+ 'application/vnd.ufdl' => 'ufd',
+ 'application/vnd.uiq.theme' => 'utz',
+ 'application/vnd.umajin' => 'umj',
+ 'application/vnd.unity' => 'unityweb',
+ 'application/vnd.uoml+xml' => 'uoml',
+ 'application/vnd.vcx' => 'vcx',
+ 'application/vnd.visio' => 'vsd',
+ 'application/vnd.visionary' => 'vis',
+ 'application/vnd.vsf' => 'vsf',
+ 'application/vnd.wap.wbxml' => 'wbxml',
+ 'application/vnd.wap.wmlc' => 'wmlc',
+ 'application/vnd.wap.wmlscriptc' => 'wmlsc',
+ 'application/vnd.webturbo' => 'wtb',
+ 'application/vnd.wolfram.player' => 'nbp',
+ 'application/vnd.wordperfect' => 'wpd',
+ 'application/vnd.wqd' => 'wqd',
+ 'application/vnd.wt.stf' => 'stf',
+ 'application/vnd.xara' => 'xar',
+ 'application/vnd.xfdl' => 'xfdl',
+ 'application/vnd.yamaha.hv-dic' => 'hvd',
+ 'application/vnd.yamaha.hv-script' => 'hvs',
+ 'application/vnd.yamaha.hv-voice' => 'hvp',
+ 'application/vnd.yamaha.openscoreformat' => 'osf',
+ 'application/vnd.yamaha.openscoreformat.osfpvg+xml' => 'osfpvg',
+ 'application/vnd.yamaha.smaf-audio' => 'saf',
+ 'application/vnd.yamaha.smaf-phrase' => 'spf',
+ 'application/vnd.yellowriver-custom-menu' => 'cmp',
+ 'application/vnd.zul' => 'zir',
+ 'application/vnd.zzazz.deck+xml' => 'zaz',
+ 'application/voicexml+xml' => 'vxml',
+ 'application/widget' => 'wgt',
+ 'application/winhlp' => 'hlp',
+ 'application/wsdl+xml' => 'wsdl',
+ 'application/wspolicy+xml' => 'wspolicy',
+ 'application/x-7z-compressed' => '7z',
+ 'application/x-abiword' => 'abw',
+ 'application/x-ace-compressed' => 'ace',
+ 'application/x-apple-diskimage' => 'dmg',
+ 'application/x-authorware-bin' => 'aab',
+ 'application/x-authorware-map' => 'aam',
+ 'application/x-authorware-seg' => 'aas',
+ 'application/x-bcpio' => 'bcpio',
+ 'application/x-bittorrent' => 'torrent',
+ 'application/x-blorb' => 'blb',
+ 'application/x-bzip' => 'bz',
+ 'application/x-bzip2' => 'bz2',
+ 'application/x-cbr' => 'cbr',
+ 'application/x-cdlink' => 'vcd',
+ 'application/x-cfs-compressed' => 'cfs',
+ 'application/x-chat' => 'chat',
+ 'application/x-chess-pgn' => 'pgn',
+ 'application/x-conference' => 'nsc',
+ 'application/x-cpio' => 'cpio',
+ 'application/x-csh' => 'csh',
+ 'application/x-debian-package' => 'deb',
+ 'application/x-dgc-compressed' => 'dgc',
+ 'application/x-director' => 'dir',
+ 'application/x-doom' => 'wad',
+ 'application/x-dtbncx+xml' => 'ncx',
+ 'application/x-dtbook+xml' => 'dtb',
+ 'application/x-dtbresource+xml' => 'res',
+ 'application/x-dvi' => 'dvi',
+ 'application/x-envoy' => 'evy',
+ 'application/x-eva' => 'eva',
+ 'application/x-font-bdf' => 'bdf',
+ 'application/x-font-ghostscript' => 'gsf',
+ 'application/x-font-linux-psf' => 'psf',
+ 'application/x-font-otf' => 'otf',
+ 'application/x-font-pcf' => 'pcf',
+ 'application/x-font-snf' => 'snf',
+ 'application/x-font-ttf' => 'ttf',
+ 'application/x-font-type1' => 'pfa',
+ 'application/x-font-woff' => 'woff',
+ 'application/x-freearc' => 'arc',
+ 'application/x-futuresplash' => 'spl',
+ 'application/x-gca-compressed' => 'gca',
+ 'application/x-glulx' => 'ulx',
+ 'application/x-gnumeric' => 'gnumeric',
+ 'application/x-gramps-xml' => 'gramps',
+ 'application/x-gtar' => 'gtar',
+ 'application/x-hdf' => 'hdf',
+ 'application/x-install-instructions' => 'install',
+ 'application/x-iso9660-image' => 'iso',
+ 'application/x-java-jnlp-file' => 'jnlp',
+ 'application/x-latex' => 'latex',
+ 'application/x-lzh-compressed' => 'lzh',
+ 'application/x-mie' => 'mie',
+ 'application/x-mobipocket-ebook' => 'prc',
+ 'application/x-ms-application' => 'application',
+ 'application/x-ms-shortcut' => 'lnk',
+ 'application/x-ms-wmd' => 'wmd',
+ 'application/x-ms-wmz' => 'wmz',
+ 'application/x-ms-xbap' => 'xbap',
+ 'application/x-msaccess' => 'mdb',
+ 'application/x-msbinder' => 'obd',
+ 'application/x-mscardfile' => 'crd',
+ 'application/x-msclip' => 'clp',
+ 'application/x-msdownload' => 'exe',
+ 'application/x-msmediaview' => 'mvb',
+ 'application/x-msmetafile' => 'wmf',
+ 'application/x-msmoney' => 'mny',
+ 'application/x-mspublisher' => 'pub',
+ 'application/x-msschedule' => 'scd',
+ 'application/x-msterminal' => 'trm',
+ 'application/x-mswrite' => 'wri',
+ 'application/x-netcdf' => 'nc',
+ 'application/x-nzb' => 'nzb',
+ 'application/x-pkcs12' => 'p12',
+ 'application/x-pkcs7-certificates' => 'p7b',
+ 'application/x-pkcs7-certreqresp' => 'p7r',
+ 'application/x-rar-compressed' => 'rar',
+ 'application/x-rar' => 'rar',
+ 'application/x-research-info-systems' => 'ris',
+ 'application/x-sh' => 'sh',
+ 'application/x-shar' => 'shar',
+ 'application/x-shockwave-flash' => 'swf',
+ 'application/x-silverlight-app' => 'xap',
+ 'application/x-sql' => 'sql',
+ 'application/x-stuffit' => 'sit',
+ 'application/x-stuffitx' => 'sitx',
+ 'application/x-subrip' => 'srt',
+ 'application/x-sv4cpio' => 'sv4cpio',
+ 'application/x-sv4crc' => 'sv4crc',
+ 'application/x-t3vm-image' => 't3',
+ 'application/x-tads' => 'gam',
+ 'application/x-tar' => 'tar',
+ 'application/x-tcl' => 'tcl',
+ 'application/x-tex' => 'tex',
+ 'application/x-tex-tfm' => 'tfm',
+ 'application/x-texinfo' => 'texinfo',
+ 'application/x-tgif' => 'obj',
+ 'application/x-ustar' => 'ustar',
+ 'application/x-wais-source' => 'src',
+ 'application/x-x509-ca-cert' => 'der',
+ 'application/x-xfig' => 'fig',
+ 'application/x-xliff+xml' => 'xlf',
+ 'application/x-xpinstall' => 'xpi',
+ 'application/x-xz' => 'xz',
+ 'application/x-zip-compressed' => 'zip',
+ 'application/x-zmachine' => 'z1',
+ 'application/xaml+xml' => 'xaml',
+ 'application/xcap-diff+xml' => 'xdf',
+ 'application/xenc+xml' => 'xenc',
+ 'application/xhtml+xml' => 'xhtml',
+ 'application/xml' => 'xml',
+ 'application/xml-dtd' => 'dtd',
+ 'application/xop+xml' => 'xop',
+ 'application/xproc+xml' => 'xpl',
+ 'application/xslt+xml' => 'xslt',
+ 'application/xspf+xml' => 'xspf',
+ 'application/xv+xml' => 'mxml',
+ 'application/yang' => 'yang',
+ 'application/yin+xml' => 'yin',
+ 'application/zip' => 'zip',
+ 'audio/adpcm' => 'adp',
+ 'audio/basic' => 'au',
+ 'audio/midi' => 'mid',
+ 'audio/mp4' => 'mp4a',
+ 'audio/mpeg' => 'mpga',
+ 'audio/ogg' => 'oga',
+ 'audio/s3m' => 's3m',
+ 'audio/silk' => 'sil',
+ 'audio/vnd.dece.audio' => 'uva',
+ 'audio/vnd.digital-winds' => 'eol',
+ 'audio/vnd.dra' => 'dra',
+ 'audio/vnd.dts' => 'dts',
+ 'audio/vnd.dts.hd' => 'dtshd',
+ 'audio/vnd.lucent.voice' => 'lvp',
+ 'audio/vnd.ms-playready.media.pya' => 'pya',
+ 'audio/vnd.nuera.ecelp4800' => 'ecelp4800',
+ 'audio/vnd.nuera.ecelp7470' => 'ecelp7470',
+ 'audio/vnd.nuera.ecelp9600' => 'ecelp9600',
+ 'audio/vnd.rip' => 'rip',
+ 'audio/webm' => 'weba',
+ 'audio/x-aac' => 'aac',
+ 'audio/x-aiff' => 'aif',
+ 'audio/x-caf' => 'caf',
+ 'audio/x-flac' => 'flac',
+ 'audio/x-matroska' => 'mka',
+ 'audio/x-mpegurl' => 'm3u',
+ 'audio/x-ms-wax' => 'wax',
+ 'audio/x-ms-wma' => 'wma',
+ 'audio/x-pn-realaudio' => 'ram',
+ 'audio/x-pn-realaudio-plugin' => 'rmp',
+ 'audio/x-wav' => 'wav',
+ 'audio/xm' => 'xm',
+ 'chemical/x-cdx' => 'cdx',
+ 'chemical/x-cif' => 'cif',
+ 'chemical/x-cmdf' => 'cmdf',
+ 'chemical/x-cml' => 'cml',
+ 'chemical/x-csml' => 'csml',
+ 'chemical/x-xyz' => 'xyz',
+ 'image/bmp' => 'bmp',
+ 'image/x-ms-bmp' => 'bmp',
+ 'image/cgm' => 'cgm',
+ 'image/g3fax' => 'g3',
+ 'image/gif' => 'gif',
+ 'image/ief' => 'ief',
+ 'image/jpeg' => 'jpeg',
+ 'image/pjpeg' => 'jpeg',
+ 'image/ktx' => 'ktx',
+ 'image/png' => 'png',
+ 'image/prs.btif' => 'btif',
+ 'image/sgi' => 'sgi',
+ 'image/svg+xml' => 'svg',
+ 'image/tiff' => 'tiff',
+ 'image/vnd.adobe.photoshop' => 'psd',
+ 'image/vnd.dece.graphic' => 'uvi',
+ 'image/vnd.dvb.subtitle' => 'sub',
+ 'image/vnd.djvu' => 'djvu',
+ 'image/vnd.dwg' => 'dwg',
+ 'image/vnd.dxf' => 'dxf',
+ 'image/vnd.fastbidsheet' => 'fbs',
+ 'image/vnd.fpx' => 'fpx',
+ 'image/vnd.fst' => 'fst',
+ 'image/vnd.fujixerox.edmics-mmr' => 'mmr',
+ 'image/vnd.fujixerox.edmics-rlc' => 'rlc',
+ 'image/vnd.ms-modi' => 'mdi',
+ 'image/vnd.ms-photo' => 'wdp',
+ 'image/vnd.net-fpx' => 'npx',
+ 'image/vnd.wap.wbmp' => 'wbmp',
+ 'image/vnd.xiff' => 'xif',
+ 'image/webp' => 'webp',
+ 'image/x-3ds' => '3ds',
+ 'image/x-cmu-raster' => 'ras',
+ 'image/x-cmx' => 'cmx',
+ 'image/x-freehand' => 'fh',
+ 'image/x-icon' => 'ico',
+ 'image/x-mrsid-image' => 'sid',
+ 'image/x-pcx' => 'pcx',
+ 'image/x-pict' => 'pic',
+ 'image/x-portable-anymap' => 'pnm',
+ 'image/x-portable-bitmap' => 'pbm',
+ 'image/x-portable-graymap' => 'pgm',
+ 'image/x-portable-pixmap' => 'ppm',
+ 'image/x-rgb' => 'rgb',
+ 'image/x-tga' => 'tga',
+ 'image/x-xbitmap' => 'xbm',
+ 'image/x-xpixmap' => 'xpm',
+ 'image/x-xwindowdump' => 'xwd',
+ 'message/rfc822' => 'eml',
+ 'model/iges' => 'igs',
+ 'model/mesh' => 'msh',
+ 'model/vnd.collada+xml' => 'dae',
+ 'model/vnd.dwf' => 'dwf',
+ 'model/vnd.gdl' => 'gdl',
+ 'model/vnd.gtw' => 'gtw',
+ 'model/vnd.mts' => 'mts',
+ 'model/vnd.vtu' => 'vtu',
+ 'model/vrml' => 'wrl',
+ 'model/x3d+binary' => 'x3db',
+ 'model/x3d+vrml' => 'x3dv',
+ 'model/x3d+xml' => 'x3d',
+ 'text/cache-manifest' => 'appcache',
+ 'text/calendar' => 'ics',
+ 'text/css' => 'css',
+ 'text/csv' => 'csv',
+ 'text/html' => 'html',
+ 'text/n3' => 'n3',
+ 'text/plain' => 'txt',
+ 'text/prs.lines.tag' => 'dsc',
+ 'text/richtext' => 'rtx',
+ 'text/rtf' => 'rtf',
+ 'text/sgml' => 'sgml',
+ 'text/tab-separated-values' => 'tsv',
+ 'text/troff' => 't',
+ 'text/turtle' => 'ttl',
+ 'text/uri-list' => 'uri',
+ 'text/vcard' => 'vcard',
+ 'text/vnd.curl' => 'curl',
+ 'text/vnd.curl.dcurl' => 'dcurl',
+ 'text/vnd.curl.scurl' => 'scurl',
+ 'text/vnd.curl.mcurl' => 'mcurl',
+ 'text/vnd.dvb.subtitle' => 'sub',
+ 'text/vnd.fly' => 'fly',
+ 'text/vnd.fmi.flexstor' => 'flx',
+ 'text/vnd.graphviz' => 'gv',
+ 'text/vnd.in3d.3dml' => '3dml',
+ 'text/vnd.in3d.spot' => 'spot',
+ 'text/vnd.sun.j2me.app-descriptor' => 'jad',
+ 'text/vnd.wap.wml' => 'wml',
+ 'text/vnd.wap.wmlscript' => 'wmls',
+ 'text/x-asm' => 's',
+ 'text/x-c' => 'c',
+ 'text/x-fortran' => 'f',
+ 'text/x-pascal' => 'p',
+ 'text/x-java-source' => 'java',
+ 'text/x-opml' => 'opml',
+ 'text/x-nfo' => 'nfo',
+ 'text/x-setext' => 'etx',
+ 'text/x-sfv' => 'sfv',
+ 'text/x-uuencode' => 'uu',
+ 'text/x-vcalendar' => 'vcs',
+ 'text/x-vcard' => 'vcf',
+ 'video/3gpp' => '3gp',
+ 'video/3gpp2' => '3g2',
+ 'video/h261' => 'h261',
+ 'video/h263' => 'h263',
+ 'video/h264' => 'h264',
+ 'video/jpeg' => 'jpgv',
+ 'video/jpm' => 'jpm',
+ 'video/mj2' => 'mj2',
+ 'video/mp4' => 'mp4',
+ 'video/mpeg' => 'mpeg',
+ 'video/ogg' => 'ogv',
+ 'video/quicktime' => 'qt',
+ 'video/vnd.dece.hd' => 'uvh',
+ 'video/vnd.dece.mobile' => 'uvm',
+ 'video/vnd.dece.pd' => 'uvp',
+ 'video/vnd.dece.sd' => 'uvs',
+ 'video/vnd.dece.video' => 'uvv',
+ 'video/vnd.dvb.file' => 'dvb',
+ 'video/vnd.fvt' => 'fvt',
+ 'video/vnd.mpegurl' => 'mxu',
+ 'video/vnd.ms-playready.media.pyv' => 'pyv',
+ 'video/vnd.uvvu.mp4' => 'uvu',
+ 'video/vnd.vivo' => 'viv',
+ 'video/webm' => 'webm',
+ 'video/x-f4v' => 'f4v',
+ 'video/x-fli' => 'fli',
+ 'video/x-flv' => 'flv',
+ 'video/x-m4v' => 'm4v',
+ 'video/x-matroska' => 'mkv',
+ 'video/x-mng' => 'mng',
+ 'video/x-ms-asf' => 'asf',
+ 'video/x-ms-vob' => 'vob',
+ 'video/x-ms-wm' => 'wm',
+ 'video/x-ms-wmv' => 'wmv',
+ 'video/x-ms-wmx' => 'wmx',
+ 'video/x-ms-wvx' => 'wvx',
+ 'video/x-msvideo' => 'avi',
+ 'video/x-sgi-movie' => 'movie',
+ 'video/x-smv' => 'smv',
+ 'x-conference/x-cooltalk' => 'ice',
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ public function guess($mimeType)
+ {
+ return isset($this->defaultExtensions[$mimeType]) ? $this->defaultExtensions[$mimeType] : null;
+ }
+}
diff --git a/console/skel/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php b/console/skel/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php
new file mode 100644
index 0000000..dae47a7
--- /dev/null
+++ b/console/skel/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php
@@ -0,0 +1,133 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+
+/**
+ * A singleton mime type guesser.
+ *
+ * By default, all mime type guessers provided by the framework are installed
+ * (if available on the current OS/PHP setup).
+ *
+ * You can register custom guessers by calling the register() method on the
+ * singleton instance. Custom guessers are always called before any default ones.
+ *
+ * $guesser = MimeTypeGuesser::getInstance();
+ * $guesser->register(new MyCustomMimeTypeGuesser());
+ *
+ * If you want to change the order of the default guessers, just re-register your
+ * preferred one as a custom one. The last registered guesser is preferred over
+ * previously registered ones.
+ *
+ * Re-registering a built-in guesser also allows you to configure it:
+ *
+ * $guesser = MimeTypeGuesser::getInstance();
+ * $guesser->register(new FileinfoMimeTypeGuesser('/path/to/magic/file'));
+ *
+ * @author Bernhard Schussek
+ */
+class MimeTypeGuesser implements MimeTypeGuesserInterface
+{
+ /**
+ * The singleton instance.
+ *
+ * @var MimeTypeGuesser
+ */
+ private static $instance = null;
+
+ /**
+ * All registered MimeTypeGuesserInterface instances.
+ *
+ * @var array
+ */
+ protected $guessers = array();
+
+ /**
+ * Returns the singleton instance.
+ *
+ * @return self
+ */
+ public static function getInstance()
+ {
+ if (null === self::$instance) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Resets the singleton instance.
+ */
+ public static function reset()
+ {
+ self::$instance = null;
+ }
+
+ /**
+ * Registers all natively provided mime type guessers.
+ */
+ private function __construct()
+ {
+ $this->register(new FileBinaryMimeTypeGuesser());
+ $this->register(new FileinfoMimeTypeGuesser());
+ }
+
+ /**
+ * Registers a new mime type guesser.
+ *
+ * When guessing, this guesser is preferred over previously registered ones.
+ */
+ public function register(MimeTypeGuesserInterface $guesser)
+ {
+ array_unshift($this->guessers, $guesser);
+ }
+
+ /**
+ * Tries to guess the mime type of the given file.
+ *
+ * The file is passed to each registered mime type guesser in reverse order
+ * of their registration (last registered is queried first). Once a guesser
+ * returns a value that is not NULL, this method terminates and returns the
+ * value.
+ *
+ * @param string $path The path to the file
+ *
+ * @return string The mime type or NULL, if none could be guessed
+ *
+ * @throws \LogicException
+ * @throws FileNotFoundException
+ * @throws AccessDeniedException
+ */
+ public function guess($path)
+ {
+ if (!is_file($path)) {
+ throw new FileNotFoundException($path);
+ }
+
+ if (!is_readable($path)) {
+ throw new AccessDeniedException($path);
+ }
+
+ foreach ($this->guessers as $guesser) {
+ if (null !== $mimeType = $guesser->guess($path)) {
+ return $mimeType;
+ }
+ }
+
+ if (2 === \count($this->guessers) && !FileBinaryMimeTypeGuesser::isSupported() && !FileinfoMimeTypeGuesser::isSupported()) {
+ throw new \LogicException('Unable to guess the mime type as no guessers are available (Did you enable the php_fileinfo extension?)');
+ }
+ }
+}
diff --git a/console/skel/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php b/console/skel/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php
new file mode 100644
index 0000000..5ac1acb
--- /dev/null
+++ b/console/skel/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+
+/**
+ * Guesses the mime type of a file.
+ *
+ * @author Bernhard Schussek
+ */
+interface MimeTypeGuesserInterface
+{
+ /**
+ * Guesses the mime type of the file with the given path.
+ *
+ * @param string $path The path to the file
+ *
+ * @return string The mime type or NULL, if none could be guessed
+ *
+ * @throws FileNotFoundException If the file does not exist
+ * @throws AccessDeniedException If the file could not be read
+ */
+ public function guess($path);
+}
diff --git a/console/skel/symfony/http-foundation/File/UploadedFile.php b/console/skel/symfony/http-foundation/File/UploadedFile.php
new file mode 100644
index 0000000..de6ce75
--- /dev/null
+++ b/console/skel/symfony/http-foundation/File/UploadedFile.php
@@ -0,0 +1,268 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser;
+
+/**
+ * A file uploaded through a form.
+ *
+ * @author Bernhard Schussek
+ * @author Florian Eckerstorfer
+ * @author Fabien Potencier
+ */
+class UploadedFile extends File
+{
+ private $test = false;
+ private $originalName;
+ private $mimeType;
+ private $size;
+ private $error;
+
+ /**
+ * Accepts the information of the uploaded file as provided by the PHP global $_FILES.
+ *
+ * The file object is only created when the uploaded file is valid (i.e. when the
+ * isValid() method returns true). Otherwise the only methods that could be called
+ * on an UploadedFile instance are:
+ *
+ * * getClientOriginalName,
+ * * getClientMimeType,
+ * * isValid,
+ * * getError.
+ *
+ * Calling any other method on an non-valid instance will cause an unpredictable result.
+ *
+ * @param string $path The full temporary path to the file
+ * @param string $originalName The original file name of the uploaded file
+ * @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream
+ * @param int|null $size The file size provided by the uploader
+ * @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK
+ * @param bool $test Whether the test mode is active
+ * Local files are used in test mode hence the code should not enforce HTTP uploads
+ *
+ * @throws FileException If file_uploads is disabled
+ * @throws FileNotFoundException If the file does not exist
+ */
+ public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false)
+ {
+ $this->originalName = $this->getName($originalName);
+ $this->mimeType = $mimeType ?: 'application/octet-stream';
+ $this->size = $size;
+ $this->error = $error ?: UPLOAD_ERR_OK;
+ $this->test = (bool) $test;
+
+ parent::__construct($path, UPLOAD_ERR_OK === $this->error);
+ }
+
+ /**
+ * Returns the original file name.
+ *
+ * It is extracted from the request from which the file has been uploaded.
+ * Then it should not be considered as a safe value.
+ *
+ * @return string|null The original name
+ */
+ public function getClientOriginalName()
+ {
+ return $this->originalName;
+ }
+
+ /**
+ * Returns the original file extension.
+ *
+ * It is extracted from the original file name that was uploaded.
+ * Then it should not be considered as a safe value.
+ *
+ * @return string The extension
+ */
+ public function getClientOriginalExtension()
+ {
+ return pathinfo($this->originalName, PATHINFO_EXTENSION);
+ }
+
+ /**
+ * Returns the file mime type.
+ *
+ * The client mime type is extracted from the request from which the file
+ * was uploaded, so it should not be considered as a safe value.
+ *
+ * For a trusted mime type, use getMimeType() instead (which guesses the mime
+ * type based on the file content).
+ *
+ * @return string|null The mime type
+ *
+ * @see getMimeType()
+ */
+ public function getClientMimeType()
+ {
+ return $this->mimeType;
+ }
+
+ /**
+ * Returns the extension based on the client mime type.
+ *
+ * If the mime type is unknown, returns null.
+ *
+ * This method uses the mime type as guessed by getClientMimeType()
+ * to guess the file extension. As such, the extension returned
+ * by this method cannot be trusted.
+ *
+ * For a trusted extension, use guessExtension() instead (which guesses
+ * the extension based on the guessed mime type for the file).
+ *
+ * @return string|null The guessed extension or null if it cannot be guessed
+ *
+ * @see guessExtension()
+ * @see getClientMimeType()
+ */
+ public function guessClientExtension()
+ {
+ $type = $this->getClientMimeType();
+ $guesser = ExtensionGuesser::getInstance();
+
+ return $guesser->guess($type);
+ }
+
+ /**
+ * Returns the file size.
+ *
+ * It is extracted from the request from which the file has been uploaded.
+ * Then it should not be considered as a safe value.
+ *
+ * @return int|null The file size
+ */
+ public function getClientSize()
+ {
+ return $this->size;
+ }
+
+ /**
+ * Returns the upload error.
+ *
+ * If the upload was successful, the constant UPLOAD_ERR_OK is returned.
+ * Otherwise one of the other UPLOAD_ERR_XXX constants is returned.
+ *
+ * @return int The upload error
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * Returns whether the file was uploaded successfully.
+ *
+ * @return bool True if the file has been uploaded with HTTP and no error occurred
+ */
+ public function isValid()
+ {
+ $isOk = UPLOAD_ERR_OK === $this->error;
+
+ return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname());
+ }
+
+ /**
+ * Moves the file to a new location.
+ *
+ * @param string $directory The destination folder
+ * @param string $name The new file name
+ *
+ * @return File A File object representing the new file
+ *
+ * @throws FileException if, for any reason, the file could not have been moved
+ */
+ public function move($directory, $name = null)
+ {
+ if ($this->isValid()) {
+ if ($this->test) {
+ return parent::move($directory, $name);
+ }
+
+ $target = $this->getTargetFile($directory, $name);
+
+ set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
+ $moved = move_uploaded_file($this->getPathname(), $target);
+ restore_error_handler();
+ if (!$moved) {
+ throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
+ }
+
+ @chmod($target, 0666 & ~umask());
+
+ return $target;
+ }
+
+ throw new FileException($this->getErrorMessage());
+ }
+
+ /**
+ * Returns the maximum size of an uploaded file as configured in php.ini.
+ *
+ * @return int The maximum size of an uploaded file in bytes
+ */
+ public static function getMaxFilesize()
+ {
+ $iniMax = strtolower(ini_get('upload_max_filesize'));
+
+ if ('' === $iniMax) {
+ return PHP_INT_MAX;
+ }
+
+ $max = ltrim($iniMax, '+');
+ if (0 === strpos($max, '0x')) {
+ $max = \intval($max, 16);
+ } elseif (0 === strpos($max, '0')) {
+ $max = \intval($max, 8);
+ } else {
+ $max = (int) $max;
+ }
+
+ switch (substr($iniMax, -1)) {
+ case 't': $max *= 1024;
+ // no break
+ case 'g': $max *= 1024;
+ // no break
+ case 'm': $max *= 1024;
+ // no break
+ case 'k': $max *= 1024;
+ }
+
+ return $max;
+ }
+
+ /**
+ * Returns an informative upload error message.
+ *
+ * @return string The error message regarding the specified error code
+ */
+ public function getErrorMessage()
+ {
+ static $errors = array(
+ UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).',
+ UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.',
+ UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.',
+ UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
+ UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.',
+ UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.',
+ UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.',
+ );
+
+ $errorCode = $this->error;
+ $maxFilesize = UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0;
+ $message = isset($errors[$errorCode]) ? $errors[$errorCode] : 'The file "%s" was not uploaded due to an unknown error.';
+
+ return sprintf($message, $this->getClientOriginalName(), $maxFilesize);
+ }
+}
diff --git a/console/skel/symfony/http-foundation/FileBag.php b/console/skel/symfony/http-foundation/FileBag.php
new file mode 100644
index 0000000..c135ad6
--- /dev/null
+++ b/console/skel/symfony/http-foundation/FileBag.php
@@ -0,0 +1,144 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+
+/**
+ * FileBag is a container for uploaded files.
+ *
+ * @author Fabien Potencier
+ * @author Bulat Shakirzyanov
+ */
+class FileBag extends ParameterBag
+{
+ private static $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type');
+
+ /**
+ * @param array $parameters An array of HTTP files
+ */
+ public function __construct(array $parameters = array())
+ {
+ $this->replace($parameters);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function replace(array $files = array())
+ {
+ $this->parameters = array();
+ $this->add($files);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($key, $value)
+ {
+ if (!\is_array($value) && !$value instanceof UploadedFile) {
+ throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.');
+ }
+
+ parent::set($key, $this->convertFileInformation($value));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function add(array $files = array())
+ {
+ foreach ($files as $key => $file) {
+ $this->set($key, $file);
+ }
+ }
+
+ /**
+ * Converts uploaded files to UploadedFile instances.
+ *
+ * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information
+ *
+ * @return UploadedFile[]|UploadedFile|null A (multi-dimensional) array of UploadedFile instances
+ */
+ protected function convertFileInformation($file)
+ {
+ if ($file instanceof UploadedFile) {
+ return $file;
+ }
+
+ $file = $this->fixPhpFilesArray($file);
+ if (\is_array($file)) {
+ $keys = array_keys($file);
+ sort($keys);
+
+ if ($keys == self::$fileKeys) {
+ if (UPLOAD_ERR_NO_FILE == $file['error']) {
+ $file = null;
+ } else {
+ $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']);
+ }
+ } else {
+ $file = array_map(array($this, 'convertFileInformation'), $file);
+ if (array_keys($keys) === $keys) {
+ $file = array_filter($file);
+ }
+ }
+ }
+
+ return $file;
+ }
+
+ /**
+ * Fixes a malformed PHP $_FILES array.
+ *
+ * PHP has a bug that the format of the $_FILES array differs, depending on
+ * whether the uploaded file fields had normal field names or array-like
+ * field names ("normal" vs. "parent[child]").
+ *
+ * This method fixes the array to look like the "normal" $_FILES array.
+ *
+ * It's safe to pass an already converted array, in which case this method
+ * just returns the original array unmodified.
+ *
+ * @return array
+ */
+ protected function fixPhpFilesArray($data)
+ {
+ if (!\is_array($data)) {
+ return $data;
+ }
+
+ $keys = array_keys($data);
+ sort($keys);
+
+ if (self::$fileKeys != $keys || !isset($data['name']) || !\is_array($data['name'])) {
+ return $data;
+ }
+
+ $files = $data;
+ foreach (self::$fileKeys as $k) {
+ unset($files[$k]);
+ }
+
+ foreach ($data['name'] as $key => $name) {
+ $files[$key] = $this->fixPhpFilesArray(array(
+ 'error' => $data['error'][$key],
+ 'name' => $name,
+ 'type' => $data['type'][$key],
+ 'tmp_name' => $data['tmp_name'][$key],
+ 'size' => $data['size'][$key],
+ ));
+ }
+
+ return $files;
+ }
+}
diff --git a/console/skel/symfony/http-foundation/HeaderBag.php b/console/skel/symfony/http-foundation/HeaderBag.php
new file mode 100644
index 0000000..0ef7428
--- /dev/null
+++ b/console/skel/symfony/http-foundation/HeaderBag.php
@@ -0,0 +1,322 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * HeaderBag is a container for HTTP headers.
+ *
+ * @author Fabien Potencier
+ */
+class HeaderBag implements \IteratorAggregate, \Countable
+{
+ protected $headers = array();
+ protected $cacheControl = array();
+
+ /**
+ * @param array $headers An array of HTTP headers
+ */
+ public function __construct(array $headers = array())
+ {
+ foreach ($headers as $key => $values) {
+ $this->set($key, $values);
+ }
+ }
+
+ /**
+ * Returns the headers as a string.
+ *
+ * @return string The headers
+ */
+ public function __toString()
+ {
+ if (!$this->headers) {
+ return '';
+ }
+
+ $max = max(array_map('strlen', array_keys($this->headers))) + 1;
+ $content = '';
+ ksort($this->headers);
+ foreach ($this->headers as $name => $values) {
+ $name = implode('-', array_map('ucfirst', explode('-', $name)));
+ foreach ($values as $value) {
+ $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value);
+ }
+ }
+
+ return $content;
+ }
+
+ /**
+ * Returns the headers.
+ *
+ * @return array An array of headers
+ */
+ public function all()
+ {
+ return $this->headers;
+ }
+
+ /**
+ * Returns the parameter keys.
+ *
+ * @return array An array of parameter keys
+ */
+ public function keys()
+ {
+ return array_keys($this->headers);
+ }
+
+ /**
+ * Replaces the current HTTP headers by a new set.
+ *
+ * @param array $headers An array of HTTP headers
+ */
+ public function replace(array $headers = array())
+ {
+ $this->headers = array();
+ $this->add($headers);
+ }
+
+ /**
+ * Adds new headers the current HTTP headers set.
+ *
+ * @param array $headers An array of HTTP headers
+ */
+ public function add(array $headers)
+ {
+ foreach ($headers as $key => $values) {
+ $this->set($key, $values);
+ }
+ }
+
+ /**
+ * Returns a header value by name.
+ *
+ * @param string $key The header name
+ * @param string|string[]|null $default The default value
+ * @param bool $first Whether to return the first value or all header values
+ *
+ * @return string|string[]|null The first header value or default value if $first is true, an array of values otherwise
+ */
+ public function get($key, $default = null, $first = true)
+ {
+ $key = str_replace('_', '-', strtolower($key));
+
+ if (!array_key_exists($key, $this->headers)) {
+ if (null === $default) {
+ return $first ? null : array();
+ }
+
+ return $first ? $default : array($default);
+ }
+
+ if ($first) {
+ return \count($this->headers[$key]) ? $this->headers[$key][0] : $default;
+ }
+
+ return $this->headers[$key];
+ }
+
+ /**
+ * Sets a header by name.
+ *
+ * @param string $key The key
+ * @param string|string[] $values The value or an array of values
+ * @param bool $replace Whether to replace the actual value or not (true by default)
+ */
+ public function set($key, $values, $replace = true)
+ {
+ $key = str_replace('_', '-', strtolower($key));
+
+ $values = array_values((array) $values);
+
+ if (true === $replace || !isset($this->headers[$key])) {
+ $this->headers[$key] = $values;
+ } else {
+ $this->headers[$key] = array_merge($this->headers[$key], $values);
+ }
+
+ if ('cache-control' === $key) {
+ $this->cacheControl = $this->parseCacheControl(implode(', ', $this->headers[$key]));
+ }
+ }
+
+ /**
+ * Returns true if the HTTP header is defined.
+ *
+ * @param string $key The HTTP header
+ *
+ * @return bool true if the parameter exists, false otherwise
+ */
+ public function has($key)
+ {
+ return array_key_exists(str_replace('_', '-', strtolower($key)), $this->headers);
+ }
+
+ /**
+ * Returns true if the given HTTP header contains the given value.
+ *
+ * @param string $key The HTTP header name
+ * @param string $value The HTTP value
+ *
+ * @return bool true if the value is contained in the header, false otherwise
+ */
+ public function contains($key, $value)
+ {
+ return \in_array($value, $this->get($key, null, false));
+ }
+
+ /**
+ * Removes a header.
+ *
+ * @param string $key The HTTP header name
+ */
+ public function remove($key)
+ {
+ $key = str_replace('_', '-', strtolower($key));
+
+ unset($this->headers[$key]);
+
+ if ('cache-control' === $key) {
+ $this->cacheControl = array();
+ }
+ }
+
+ /**
+ * Returns the HTTP header value converted to a date.
+ *
+ * @param string $key The parameter key
+ * @param \DateTime $default The default value
+ *
+ * @return \DateTime|null The parsed DateTime or the default value if the header does not exist
+ *
+ * @throws \RuntimeException When the HTTP header is not parseable
+ */
+ public function getDate($key, \DateTime $default = null)
+ {
+ if (null === $value = $this->get($key)) {
+ return $default;
+ }
+
+ if (false === $date = \DateTime::createFromFormat(DATE_RFC2822, $value)) {
+ throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value));
+ }
+
+ return $date;
+ }
+
+ /**
+ * Adds a custom Cache-Control directive.
+ *
+ * @param string $key The Cache-Control directive name
+ * @param mixed $value The Cache-Control directive value
+ */
+ public function addCacheControlDirective($key, $value = true)
+ {
+ $this->cacheControl[$key] = $value;
+
+ $this->set('Cache-Control', $this->getCacheControlHeader());
+ }
+
+ /**
+ * Returns true if the Cache-Control directive is defined.
+ *
+ * @param string $key The Cache-Control directive
+ *
+ * @return bool true if the directive exists, false otherwise
+ */
+ public function hasCacheControlDirective($key)
+ {
+ return array_key_exists($key, $this->cacheControl);
+ }
+
+ /**
+ * Returns a Cache-Control directive value by name.
+ *
+ * @param string $key The directive name
+ *
+ * @return mixed|null The directive value if defined, null otherwise
+ */
+ public function getCacheControlDirective($key)
+ {
+ return array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null;
+ }
+
+ /**
+ * Removes a Cache-Control directive.
+ *
+ * @param string $key The Cache-Control directive
+ */
+ public function removeCacheControlDirective($key)
+ {
+ unset($this->cacheControl[$key]);
+
+ $this->set('Cache-Control', $this->getCacheControlHeader());
+ }
+
+ /**
+ * Returns an iterator for headers.
+ *
+ * @return \ArrayIterator An \ArrayIterator instance
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->headers);
+ }
+
+ /**
+ * Returns the number of headers.
+ *
+ * @return int The number of headers
+ */
+ public function count()
+ {
+ return \count($this->headers);
+ }
+
+ protected function getCacheControlHeader()
+ {
+ $parts = array();
+ ksort($this->cacheControl);
+ foreach ($this->cacheControl as $key => $value) {
+ if (true === $value) {
+ $parts[] = $key;
+ } else {
+ if (preg_match('#[^a-zA-Z0-9._-]#', $value)) {
+ $value = '"'.$value.'"';
+ }
+
+ $parts[] = "$key=$value";
+ }
+ }
+
+ return implode(', ', $parts);
+ }
+
+ /**
+ * Parses a Cache-Control HTTP header.
+ *
+ * @param string $header The value of the Cache-Control HTTP header
+ *
+ * @return array An array representing the attribute values
+ */
+ protected function parseCacheControl($header)
+ {
+ $cacheControl = array();
+ preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER);
+ foreach ($matches as $match) {
+ $cacheControl[strtolower($match[1])] = isset($match[3]) ? $match[3] : (isset($match[2]) ? $match[2] : true);
+ }
+
+ return $cacheControl;
+ }
+}
diff --git a/console/skel/symfony/http-foundation/IpUtils.php b/console/skel/symfony/http-foundation/IpUtils.php
new file mode 100644
index 0000000..a1bfa90
--- /dev/null
+++ b/console/skel/symfony/http-foundation/IpUtils.php
@@ -0,0 +1,156 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Http utility functions.
+ *
+ * @author Fabien Potencier
+ */
+class IpUtils
+{
+ private static $checkedIps = array();
+
+ /**
+ * This class should not be instantiated.
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets.
+ *
+ * @param string $requestIp IP to check
+ * @param string|array $ips List of IPs or subnets (can be a string if only a single one)
+ *
+ * @return bool Whether the IP is valid
+ */
+ public static function checkIp($requestIp, $ips)
+ {
+ if (!\is_array($ips)) {
+ $ips = array($ips);
+ }
+
+ $method = substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4';
+
+ foreach ($ips as $ip) {
+ if (self::$method($requestIp, $ip)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Compares two IPv4 addresses.
+ * In case a subnet is given, it checks if it contains the request IP.
+ *
+ * @param string $requestIp IPv4 address to check
+ * @param string $ip IPv4 address or subnet in CIDR notation
+ *
+ * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet
+ */
+ public static function checkIp4($requestIp, $ip)
+ {
+ $cacheKey = $requestIp.'-'.$ip;
+ if (isset(self::$checkedIps[$cacheKey])) {
+ return self::$checkedIps[$cacheKey];
+ }
+
+ if (!filter_var($requestIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+
+ if (false !== strpos($ip, '/')) {
+ list($address, $netmask) = explode('/', $ip, 2);
+
+ if ('0' === $netmask) {
+ return self::$checkedIps[$cacheKey] = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
+ }
+
+ if ($netmask < 0 || $netmask > 32) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+ } else {
+ $address = $ip;
+ $netmask = 32;
+ }
+
+ if (false === ip2long($address)) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+
+ return self::$checkedIps[$cacheKey] = 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask);
+ }
+
+ /**
+ * Compares two IPv6 addresses.
+ * In case a subnet is given, it checks if it contains the request IP.
+ *
+ * @author David Soria Parra
+ *
+ * @see https://github.com/dsp/v6tools
+ *
+ * @param string $requestIp IPv6 address to check
+ * @param string $ip IPv6 address or subnet in CIDR notation
+ *
+ * @return bool Whether the IP is valid
+ *
+ * @throws \RuntimeException When IPV6 support is not enabled
+ */
+ public static function checkIp6($requestIp, $ip)
+ {
+ $cacheKey = $requestIp.'-'.$ip;
+ if (isset(self::$checkedIps[$cacheKey])) {
+ return self::$checkedIps[$cacheKey];
+ }
+
+ if (!((\extension_loaded('sockets') && \defined('AF_INET6')) || @inet_pton('::1'))) {
+ throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".');
+ }
+
+ if (false !== strpos($ip, '/')) {
+ list($address, $netmask) = explode('/', $ip, 2);
+
+ if ('0' === $netmask) {
+ return (bool) unpack('n*', @inet_pton($address));
+ }
+
+ if ($netmask < 1 || $netmask > 128) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+ } else {
+ $address = $ip;
+ $netmask = 128;
+ }
+
+ $bytesAddr = unpack('n*', @inet_pton($address));
+ $bytesTest = unpack('n*', @inet_pton($requestIp));
+
+ if (!$bytesAddr || !$bytesTest) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+
+ for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) {
+ $left = $netmask - 16 * ($i - 1);
+ $left = ($left <= 16) ? $left : 16;
+ $mask = ~(0xffff >> $left) & 0xffff;
+ if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+ }
+
+ return self::$checkedIps[$cacheKey] = true;
+ }
+}
diff --git a/console/skel/symfony/http-foundation/JsonResponse.php b/console/skel/symfony/http-foundation/JsonResponse.php
new file mode 100644
index 0000000..5a9e755
--- /dev/null
+++ b/console/skel/symfony/http-foundation/JsonResponse.php
@@ -0,0 +1,218 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Response represents an HTTP response in JSON format.
+ *
+ * Note that this class does not force the returned JSON content to be an
+ * object. It is however recommended that you do return an object as it
+ * protects yourself against XSSI and JSON-JavaScript Hijacking.
+ *
+ * @see https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside
+ *
+ * @author Igor Wiedler
+ */
+class JsonResponse extends Response
+{
+ protected $data;
+ protected $callback;
+
+ // Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML.
+ // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
+ protected $encodingOptions = 15;
+
+ /**
+ * @param mixed $data The response data
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ */
+ public function __construct($data = null, $status = 200, $headers = array())
+ {
+ parent::__construct('', $status, $headers);
+
+ if (null === $data) {
+ $data = new \ArrayObject();
+ }
+
+ $this->setData($data);
+ }
+
+ /**
+ * Factory method for chainability.
+ *
+ * Example:
+ *
+ * return JsonResponse::create($data, 200)
+ * ->setSharedMaxAge(300);
+ *
+ * @param mixed $data The json response data
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ *
+ * @return static
+ */
+ public static function create($data = null, $status = 200, $headers = array())
+ {
+ return new static($data, $status, $headers);
+ }
+
+ /**
+ * Sets the JSONP callback.
+ *
+ * @param string|null $callback The JSONP callback or null to use none
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException When the callback name is not valid
+ */
+ public function setCallback($callback = null)
+ {
+ if (null !== $callback) {
+ // partially token from http://www.geekality.net/2011/08/03/valid-javascript-identifier/
+ // partially token from https://github.com/willdurand/JsonpCallbackValidator
+ // JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details.
+ // (c) William Durand
+ $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u';
+ $reserved = array(
+ 'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while',
+ 'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export',
+ 'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false',
+ );
+ $parts = explode('.', $callback);
+ foreach ($parts as $part) {
+ if (!preg_match($pattern, $part) || \in_array($part, $reserved, true)) {
+ throw new \InvalidArgumentException('The callback name is not valid.');
+ }
+ }
+ }
+
+ $this->callback = $callback;
+
+ return $this->update();
+ }
+
+ /**
+ * Sets the data to be sent as JSON.
+ *
+ * @param mixed $data
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setData($data = array())
+ {
+ if (\defined('HHVM_VERSION')) {
+ // HHVM does not trigger any warnings and let exceptions
+ // thrown from a JsonSerializable object pass through.
+ // If only PHP did the same...
+ $data = json_encode($data, $this->encodingOptions);
+ } else {
+ try {
+ if (!interface_exists('JsonSerializable', false)) {
+ // PHP 5.3 triggers annoying warnings for some
+ // types that can't be serialized as JSON (INF, resources, etc.)
+ // but doesn't provide the JsonSerializable interface.
+ set_error_handler(function () { return false; });
+ $data = @json_encode($data, $this->encodingOptions);
+ restore_error_handler();
+ } elseif (\PHP_VERSION_ID < 50500) {
+ // PHP 5.4 and up wrap exceptions thrown by JsonSerializable
+ // objects in a new exception that needs to be removed.
+ // Fortunately, PHP 5.5 and up do not trigger any warning anymore.
+ // Clear json_last_error()
+ json_encode(null);
+ $errorHandler = set_error_handler('var_dump');
+ restore_error_handler();
+ set_error_handler(function () use ($errorHandler) {
+ if (JSON_ERROR_NONE === json_last_error()) {
+ return $errorHandler && false !== \call_user_func_array($errorHandler, \func_get_args());
+ }
+ });
+ $data = json_encode($data, $this->encodingOptions);
+ restore_error_handler();
+ } else {
+ $data = json_encode($data, $this->encodingOptions);
+ }
+ } catch (\Error $e) {
+ if (\PHP_VERSION_ID < 50500 || !interface_exists('JsonSerializable', false)) {
+ restore_error_handler();
+ }
+ throw $e;
+ } catch (\Exception $e) {
+ if (\PHP_VERSION_ID < 50500 || !interface_exists('JsonSerializable', false)) {
+ restore_error_handler();
+ }
+ if (interface_exists('JsonSerializable', false) && 'Exception' === \get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) {
+ throw $e->getPrevious() ?: $e;
+ }
+ throw $e;
+ }
+ }
+
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new \InvalidArgumentException(json_last_error_msg());
+ }
+
+ $this->data = $data;
+
+ return $this->update();
+ }
+
+ /**
+ * Returns options used while encoding data to JSON.
+ *
+ * @return int
+ */
+ public function getEncodingOptions()
+ {
+ return $this->encodingOptions;
+ }
+
+ /**
+ * Sets options used while encoding data to JSON.
+ *
+ * @param int $encodingOptions
+ *
+ * @return $this
+ */
+ public function setEncodingOptions($encodingOptions)
+ {
+ $this->encodingOptions = (int) $encodingOptions;
+
+ return $this->setData(json_decode($this->data));
+ }
+
+ /**
+ * Updates the content and headers according to the JSON data and callback.
+ *
+ * @return $this
+ */
+ protected function update()
+ {
+ if (null !== $this->callback) {
+ // Not using application/javascript for compatibility reasons with older browsers.
+ $this->headers->set('Content-Type', 'text/javascript');
+
+ return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data));
+ }
+
+ // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback)
+ // in order to not overwrite a custom definition.
+ if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) {
+ $this->headers->set('Content-Type', 'application/json');
+ }
+
+ return $this->setContent($this->data);
+ }
+}
diff --git a/console/skel/symfony/http-foundation/LICENSE b/console/skel/symfony/http-foundation/LICENSE
new file mode 100644
index 0000000..21d7fb9
--- /dev/null
+++ b/console/skel/symfony/http-foundation/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2018 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/console/skel/symfony/http-foundation/ParameterBag.php b/console/skel/symfony/http-foundation/ParameterBag.php
new file mode 100644
index 0000000..6141b1b
--- /dev/null
+++ b/console/skel/symfony/http-foundation/ParameterBag.php
@@ -0,0 +1,308 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * ParameterBag is a container for key/value pairs.
+ *
+ * @author Fabien Potencier
+ */
+class ParameterBag implements \IteratorAggregate, \Countable
+{
+ /**
+ * Parameter storage.
+ */
+ protected $parameters;
+
+ /**
+ * @param array $parameters An array of parameters
+ */
+ public function __construct(array $parameters = array())
+ {
+ $this->parameters = $parameters;
+ }
+
+ /**
+ * Returns the parameters.
+ *
+ * @return array An array of parameters
+ */
+ public function all()
+ {
+ return $this->parameters;
+ }
+
+ /**
+ * Returns the parameter keys.
+ *
+ * @return array An array of parameter keys
+ */
+ public function keys()
+ {
+ return array_keys($this->parameters);
+ }
+
+ /**
+ * Replaces the current parameters by a new set.
+ *
+ * @param array $parameters An array of parameters
+ */
+ public function replace(array $parameters = array())
+ {
+ $this->parameters = $parameters;
+ }
+
+ /**
+ * Adds parameters.
+ *
+ * @param array $parameters An array of parameters
+ */
+ public function add(array $parameters = array())
+ {
+ $this->parameters = array_replace($this->parameters, $parameters);
+ }
+
+ /**
+ * Returns a parameter by name.
+ *
+ * Note: Finding deep items is deprecated since version 2.8, to be removed in 3.0.
+ *
+ * @param string $key The key
+ * @param mixed $default The default value if the parameter key does not exist
+ * @param bool $deep If true, a path like foo[bar] will find deeper items
+ *
+ * @return mixed
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function get($key, $default = null, $deep = false)
+ {
+ if ($deep) {
+ @trigger_error('Using paths to find deeper items in '.__METHOD__.' is deprecated since Symfony 2.8 and will be removed in 3.0. Filter the returned value in your own code instead.', E_USER_DEPRECATED);
+ }
+
+ if (!$deep || false === $pos = strpos($key, '[')) {
+ return array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default;
+ }
+
+ $root = substr($key, 0, $pos);
+ if (!array_key_exists($root, $this->parameters)) {
+ return $default;
+ }
+
+ $value = $this->parameters[$root];
+ $currentKey = null;
+ for ($i = $pos, $c = \strlen($key); $i < $c; ++$i) {
+ $char = $key[$i];
+
+ if ('[' === $char) {
+ if (null !== $currentKey) {
+ throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "[" at position %d.', $i));
+ }
+
+ $currentKey = '';
+ } elseif (']' === $char) {
+ if (null === $currentKey) {
+ throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "]" at position %d.', $i));
+ }
+
+ if (!\is_array($value) || !array_key_exists($currentKey, $value)) {
+ return $default;
+ }
+
+ $value = $value[$currentKey];
+ $currentKey = null;
+ } else {
+ if (null === $currentKey) {
+ throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "%s" at position %d.', $char, $i));
+ }
+
+ $currentKey .= $char;
+ }
+ }
+
+ if (null !== $currentKey) {
+ throw new \InvalidArgumentException('Malformed path. Path must end with "]".');
+ }
+
+ return $value;
+ }
+
+ /**
+ * Sets a parameter by name.
+ *
+ * @param string $key The key
+ * @param mixed $value The value
+ */
+ public function set($key, $value)
+ {
+ $this->parameters[$key] = $value;
+ }
+
+ /**
+ * Returns true if the parameter is defined.
+ *
+ * @param string $key The key
+ *
+ * @return bool true if the parameter exists, false otherwise
+ */
+ public function has($key)
+ {
+ return array_key_exists($key, $this->parameters);
+ }
+
+ /**
+ * Removes a parameter.
+ *
+ * @param string $key The key
+ */
+ public function remove($key)
+ {
+ unset($this->parameters[$key]);
+ }
+
+ /**
+ * Returns the alphabetic characters of the parameter value.
+ *
+ * @param string $key The parameter key
+ * @param string $default The default value if the parameter key does not exist
+ * @param bool $deep If true, a path like foo[bar] will find deeper items
+ *
+ * @return string The filtered value
+ */
+ public function getAlpha($key, $default = '', $deep = false)
+ {
+ return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default, $deep));
+ }
+
+ /**
+ * Returns the alphabetic characters and digits of the parameter value.
+ *
+ * @param string $key The parameter key
+ * @param string $default The default value if the parameter key does not exist
+ * @param bool $deep If true, a path like foo[bar] will find deeper items
+ *
+ * @return string The filtered value
+ */
+ public function getAlnum($key, $default = '', $deep = false)
+ {
+ return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default, $deep));
+ }
+
+ /**
+ * Returns the digits of the parameter value.
+ *
+ * @param string $key The parameter key
+ * @param string $default The default value if the parameter key does not exist
+ * @param bool $deep If true, a path like foo[bar] will find deeper items
+ *
+ * @return string The filtered value
+ */
+ public function getDigits($key, $default = '', $deep = false)
+ {
+ // we need to remove - and + because they're allowed in the filter
+ return str_replace(array('-', '+'), '', $this->filter($key, $default, FILTER_SANITIZE_NUMBER_INT, array(), $deep));
+ }
+
+ /**
+ * Returns the parameter value converted to integer.
+ *
+ * @param string $key The parameter key
+ * @param int $default The default value if the parameter key does not exist
+ * @param bool $deep If true, a path like foo[bar] will find deeper items
+ *
+ * @return int The filtered value
+ */
+ public function getInt($key, $default = 0, $deep = false)
+ {
+ return (int) $this->get($key, $default, $deep);
+ }
+
+ /**
+ * Returns the parameter value converted to boolean.
+ *
+ * @param string $key The parameter key
+ * @param bool $default The default value if the parameter key does not exist
+ * @param bool $deep If true, a path like foo[bar] will find deeper items
+ *
+ * @return bool The filtered value
+ */
+ public function getBoolean($key, $default = false, $deep = false)
+ {
+ return $this->filter($key, $default, FILTER_VALIDATE_BOOLEAN, array(), $deep);
+ }
+
+ /**
+ * Filter key.
+ *
+ * @param string $key Key
+ * @param mixed $default Default = null
+ * @param int $filter FILTER_* constant
+ * @param mixed $options Filter options
+ * @param bool $deep Default = false
+ *
+ * @see http://php.net/manual/en/function.filter-var.php
+ *
+ * @return mixed
+ */
+ public function filter($key, $default = null, $filter = FILTER_DEFAULT, $options = array(), $deep = false)
+ {
+ static $filters = null;
+
+ if (null === $filters) {
+ foreach (filter_list() as $tmp) {
+ $filters[filter_id($tmp)] = 1;
+ }
+ }
+ if (\is_bool($filter) || !isset($filters[$filter]) || \is_array($deep)) {
+ @trigger_error('Passing the $deep boolean as 3rd argument to the '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0. Remove it altogether as the $deep argument will be removed in 3.0.', E_USER_DEPRECATED);
+ $tmp = $deep;
+ $deep = $filter;
+ $filter = $options;
+ $options = $tmp;
+ }
+
+ $value = $this->get($key, $default, $deep);
+
+ // Always turn $options into an array - this allows filter_var option shortcuts.
+ if (!\is_array($options) && $options) {
+ $options = array('flags' => $options);
+ }
+
+ // Add a convenience check for arrays.
+ if (\is_array($value) && !isset($options['flags'])) {
+ $options['flags'] = FILTER_REQUIRE_ARRAY;
+ }
+
+ return filter_var($value, $filter, $options);
+ }
+
+ /**
+ * Returns an iterator for parameters.
+ *
+ * @return \ArrayIterator An \ArrayIterator instance
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->parameters);
+ }
+
+ /**
+ * Returns the number of parameters.
+ *
+ * @return int The number of parameters
+ */
+ public function count()
+ {
+ return \count($this->parameters);
+ }
+}
diff --git a/console/skel/symfony/http-foundation/README.md b/console/skel/symfony/http-foundation/README.md
new file mode 100644
index 0000000..8907f0b
--- /dev/null
+++ b/console/skel/symfony/http-foundation/README.md
@@ -0,0 +1,14 @@
+HttpFoundation Component
+========================
+
+The HttpFoundation component defines an object-oriented layer for the HTTP
+specification.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/http_foundation/index.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/console/skel/symfony/http-foundation/RedirectResponse.php b/console/skel/symfony/http-foundation/RedirectResponse.php
new file mode 100644
index 0000000..23eb04a
--- /dev/null
+++ b/console/skel/symfony/http-foundation/RedirectResponse.php
@@ -0,0 +1,105 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * RedirectResponse represents an HTTP response doing a redirect.
+ *
+ * @author Fabien Potencier
+ */
+class RedirectResponse extends Response
+{
+ protected $targetUrl;
+
+ /**
+ * Creates a redirect response so that it conforms to the rules defined for a redirect status code.
+ *
+ * @param string $url The URL to redirect to. The URL should be a full URL, with schema etc.,
+ * but practically every browser redirects on paths only as well
+ * @param int $status The status code (302 by default)
+ * @param array $headers The headers (Location is always set to the given URL)
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @see http://tools.ietf.org/html/rfc2616#section-10.3
+ */
+ public function __construct($url, $status = 302, $headers = array())
+ {
+ parent::__construct('', $status, $headers);
+
+ $this->setTargetUrl($url);
+
+ if (!$this->isRedirect()) {
+ throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
+ }
+ }
+
+ /**
+ * Factory method for chainability.
+ *
+ * @param string $url The url to redirect to
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ *
+ * @return static
+ */
+ public static function create($url = '', $status = 302, $headers = array())
+ {
+ return new static($url, $status, $headers);
+ }
+
+ /**
+ * Returns the target URL.
+ *
+ * @return string target URL
+ */
+ public function getTargetUrl()
+ {
+ return $this->targetUrl;
+ }
+
+ /**
+ * Sets the redirect target of this response.
+ *
+ * @param string $url The URL to redirect to
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setTargetUrl($url)
+ {
+ if (empty($url)) {
+ throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
+ }
+
+ $this->targetUrl = $url;
+
+ $this->setContent(
+ sprintf('
+
+
+
+
+
+ Redirecting to %1$s
+
+
+ Redirecting to %1$s .
+
+', htmlspecialchars($url, ENT_QUOTES, 'UTF-8')));
+
+ $this->headers->set('Location', $url);
+
+ return $this;
+ }
+}
diff --git a/console/skel/symfony/http-foundation/Request.php b/console/skel/symfony/http-foundation/Request.php
new file mode 100644
index 0000000..b40856b
--- /dev/null
+++ b/console/skel/symfony/http-foundation/Request.php
@@ -0,0 +1,2034 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException;
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+
+/**
+ * Request represents an HTTP request.
+ *
+ * The methods dealing with URL accept / return a raw path (% encoded):
+ * * getBasePath
+ * * getBaseUrl
+ * * getPathInfo
+ * * getRequestUri
+ * * getUri
+ * * getUriForPath
+ *
+ * @author Fabien Potencier
+ */
+class Request
+{
+ const HEADER_FORWARDED = 'forwarded';
+ const HEADER_CLIENT_IP = 'client_ip';
+ const HEADER_CLIENT_HOST = 'client_host';
+ const HEADER_CLIENT_PROTO = 'client_proto';
+ const HEADER_CLIENT_PORT = 'client_port';
+
+ const METHOD_HEAD = 'HEAD';
+ const METHOD_GET = 'GET';
+ const METHOD_POST = 'POST';
+ const METHOD_PUT = 'PUT';
+ const METHOD_PATCH = 'PATCH';
+ const METHOD_DELETE = 'DELETE';
+ const METHOD_PURGE = 'PURGE';
+ const METHOD_OPTIONS = 'OPTIONS';
+ const METHOD_TRACE = 'TRACE';
+ const METHOD_CONNECT = 'CONNECT';
+
+ /**
+ * @var string[]
+ */
+ protected static $trustedProxies = array();
+
+ /**
+ * @var string[]
+ */
+ protected static $trustedHostPatterns = array();
+
+ /**
+ * @var string[]
+ */
+ protected static $trustedHosts = array();
+
+ /**
+ * Names for headers that can be trusted when
+ * using trusted proxies.
+ *
+ * The FORWARDED header is the standard as of rfc7239.
+ *
+ * The other headers are non-standard, but widely used
+ * by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
+ */
+ protected static $trustedHeaders = array(
+ self::HEADER_FORWARDED => 'FORWARDED',
+ self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
+ self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
+ self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
+ self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
+ );
+
+ protected static $httpMethodParameterOverride = false;
+
+ /**
+ * Custom parameters.
+ *
+ * @var \Symfony\Component\HttpFoundation\ParameterBag
+ */
+ public $attributes;
+
+ /**
+ * Request body parameters ($_POST).
+ *
+ * @var \Symfony\Component\HttpFoundation\ParameterBag
+ */
+ public $request;
+
+ /**
+ * Query string parameters ($_GET).
+ *
+ * @var \Symfony\Component\HttpFoundation\ParameterBag
+ */
+ public $query;
+
+ /**
+ * Server and execution environment parameters ($_SERVER).
+ *
+ * @var \Symfony\Component\HttpFoundation\ServerBag
+ */
+ public $server;
+
+ /**
+ * Uploaded files ($_FILES).
+ *
+ * @var \Symfony\Component\HttpFoundation\FileBag
+ */
+ public $files;
+
+ /**
+ * Cookies ($_COOKIE).
+ *
+ * @var \Symfony\Component\HttpFoundation\ParameterBag
+ */
+ public $cookies;
+
+ /**
+ * Headers (taken from the $_SERVER).
+ *
+ * @var \Symfony\Component\HttpFoundation\HeaderBag
+ */
+ public $headers;
+
+ /**
+ * @var string|resource|false|null
+ */
+ protected $content;
+
+ /**
+ * @var array
+ */
+ protected $languages;
+
+ /**
+ * @var array
+ */
+ protected $charsets;
+
+ /**
+ * @var array
+ */
+ protected $encodings;
+
+ /**
+ * @var array
+ */
+ protected $acceptableContentTypes;
+
+ /**
+ * @var string
+ */
+ protected $pathInfo;
+
+ /**
+ * @var string
+ */
+ protected $requestUri;
+
+ /**
+ * @var string
+ */
+ protected $baseUrl;
+
+ /**
+ * @var string
+ */
+ protected $basePath;
+
+ /**
+ * @var string
+ */
+ protected $method;
+
+ /**
+ * @var string
+ */
+ protected $format;
+
+ /**
+ * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
+ */
+ protected $session;
+
+ /**
+ * @var string
+ */
+ protected $locale;
+
+ /**
+ * @var string
+ */
+ protected $defaultLocale = 'en';
+
+ /**
+ * @var array
+ */
+ protected static $formats;
+
+ protected static $requestFactory;
+
+ private $isForwardedValid = true;
+
+ private static $forwardedParams = array(
+ self::HEADER_CLIENT_IP => 'for',
+ self::HEADER_CLIENT_HOST => 'host',
+ self::HEADER_CLIENT_PROTO => 'proto',
+ self::HEADER_CLIENT_PORT => 'host',
+ );
+
+ /**
+ * @param array $query The GET parameters
+ * @param array $request The POST parameters
+ * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+ * @param array $cookies The COOKIE parameters
+ * @param array $files The FILES parameters
+ * @param array $server The SERVER parameters
+ * @param string|resource|null $content The raw body data
+ */
+ public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
+ {
+ $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content);
+ }
+
+ /**
+ * Sets the parameters for this request.
+ *
+ * This method also re-initializes all properties.
+ *
+ * @param array $query The GET parameters
+ * @param array $request The POST parameters
+ * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+ * @param array $cookies The COOKIE parameters
+ * @param array $files The FILES parameters
+ * @param array $server The SERVER parameters
+ * @param string|resource|null $content The raw body data
+ */
+ public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
+ {
+ $this->request = new ParameterBag($request);
+ $this->query = new ParameterBag($query);
+ $this->attributes = new ParameterBag($attributes);
+ $this->cookies = new ParameterBag($cookies);
+ $this->files = new FileBag($files);
+ $this->server = new ServerBag($server);
+ $this->headers = new HeaderBag($this->server->getHeaders());
+
+ $this->content = $content;
+ $this->languages = null;
+ $this->charsets = null;
+ $this->encodings = null;
+ $this->acceptableContentTypes = null;
+ $this->pathInfo = null;
+ $this->requestUri = null;
+ $this->baseUrl = null;
+ $this->basePath = null;
+ $this->method = null;
+ $this->format = null;
+ }
+
+ /**
+ * Creates a new request with values from PHP's super globals.
+ *
+ * @return static
+ */
+ public static function createFromGlobals()
+ {
+ // With the php's bug #66606, the php's built-in web server
+ // stores the Content-Type and Content-Length header values in
+ // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields.
+ $server = $_SERVER;
+ if ('cli-server' === \PHP_SAPI) {
+ if (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) {
+ $server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH'];
+ }
+ if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
+ $server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE'];
+ }
+ }
+
+ $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server);
+
+ if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
+ && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
+ ) {
+ parse_str($request->getContent(), $data);
+ $request->request = new ParameterBag($data);
+ }
+
+ return $request;
+ }
+
+ /**
+ * Creates a Request based on a given URI and configuration.
+ *
+ * The information contained in the URI always take precedence
+ * over the other information (server and parameters).
+ *
+ * @param string $uri The URI
+ * @param string $method The HTTP method
+ * @param array $parameters The query (GET) or request (POST) parameters
+ * @param array $cookies The request cookies ($_COOKIE)
+ * @param array $files The request files ($_FILES)
+ * @param array $server The server parameters ($_SERVER)
+ * @param string|resource|null $content The raw body data
+ *
+ * @return static
+ */
+ public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null)
+ {
+ $server = array_replace(array(
+ 'SERVER_NAME' => 'localhost',
+ 'SERVER_PORT' => 80,
+ 'HTTP_HOST' => 'localhost',
+ 'HTTP_USER_AGENT' => 'Symfony/2.X',
+ 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+ 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5',
+ 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'SCRIPT_NAME' => '',
+ 'SCRIPT_FILENAME' => '',
+ 'SERVER_PROTOCOL' => 'HTTP/1.1',
+ 'REQUEST_TIME' => time(),
+ ), $server);
+
+ $server['PATH_INFO'] = '';
+ $server['REQUEST_METHOD'] = strtoupper($method);
+
+ $components = parse_url($uri);
+ if (isset($components['host'])) {
+ $server['SERVER_NAME'] = $components['host'];
+ $server['HTTP_HOST'] = $components['host'];
+ }
+
+ if (isset($components['scheme'])) {
+ if ('https' === $components['scheme']) {
+ $server['HTTPS'] = 'on';
+ $server['SERVER_PORT'] = 443;
+ } else {
+ unset($server['HTTPS']);
+ $server['SERVER_PORT'] = 80;
+ }
+ }
+
+ if (isset($components['port'])) {
+ $server['SERVER_PORT'] = $components['port'];
+ $server['HTTP_HOST'] .= ':'.$components['port'];
+ }
+
+ if (isset($components['user'])) {
+ $server['PHP_AUTH_USER'] = $components['user'];
+ }
+
+ if (isset($components['pass'])) {
+ $server['PHP_AUTH_PW'] = $components['pass'];
+ }
+
+ if (!isset($components['path'])) {
+ $components['path'] = '/';
+ }
+
+ switch (strtoupper($method)) {
+ case 'POST':
+ case 'PUT':
+ case 'DELETE':
+ if (!isset($server['CONTENT_TYPE'])) {
+ $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
+ }
+ // no break
+ case 'PATCH':
+ $request = $parameters;
+ $query = array();
+ break;
+ default:
+ $request = array();
+ $query = $parameters;
+ break;
+ }
+
+ $queryString = '';
+ if (isset($components['query'])) {
+ parse_str(html_entity_decode($components['query']), $qs);
+
+ if ($query) {
+ $query = array_replace($qs, $query);
+ $queryString = http_build_query($query, '', '&');
+ } else {
+ $query = $qs;
+ $queryString = $components['query'];
+ }
+ } elseif ($query) {
+ $queryString = http_build_query($query, '', '&');
+ }
+
+ $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : '');
+ $server['QUERY_STRING'] = $queryString;
+
+ return self::createRequestFromFactory($query, $request, array(), $cookies, $files, $server, $content);
+ }
+
+ /**
+ * Sets a callable able to create a Request instance.
+ *
+ * This is mainly useful when you need to override the Request class
+ * to keep BC with an existing system. It should not be used for any
+ * other purpose.
+ *
+ * @param callable|null $callable A PHP callable
+ */
+ public static function setFactory($callable)
+ {
+ self::$requestFactory = $callable;
+ }
+
+ /**
+ * Clones a request and overrides some of its parameters.
+ *
+ * @param array $query The GET parameters
+ * @param array $request The POST parameters
+ * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+ * @param array $cookies The COOKIE parameters
+ * @param array $files The FILES parameters
+ * @param array $server The SERVER parameters
+ *
+ * @return static
+ */
+ public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
+ {
+ $dup = clone $this;
+ if (null !== $query) {
+ $dup->query = new ParameterBag($query);
+ }
+ if (null !== $request) {
+ $dup->request = new ParameterBag($request);
+ }
+ if (null !== $attributes) {
+ $dup->attributes = new ParameterBag($attributes);
+ }
+ if (null !== $cookies) {
+ $dup->cookies = new ParameterBag($cookies);
+ }
+ if (null !== $files) {
+ $dup->files = new FileBag($files);
+ }
+ if (null !== $server) {
+ $dup->server = new ServerBag($server);
+ $dup->headers = new HeaderBag($dup->server->getHeaders());
+ }
+ $dup->languages = null;
+ $dup->charsets = null;
+ $dup->encodings = null;
+ $dup->acceptableContentTypes = null;
+ $dup->pathInfo = null;
+ $dup->requestUri = null;
+ $dup->baseUrl = null;
+ $dup->basePath = null;
+ $dup->method = null;
+ $dup->format = null;
+
+ if (!$dup->get('_format') && $this->get('_format')) {
+ $dup->attributes->set('_format', $this->get('_format'));
+ }
+
+ if (!$dup->getRequestFormat(null)) {
+ $dup->setRequestFormat($this->getRequestFormat(null));
+ }
+
+ return $dup;
+ }
+
+ /**
+ * Clones the current request.
+ *
+ * Note that the session is not cloned as duplicated requests
+ * are most of the time sub-requests of the main one.
+ */
+ public function __clone()
+ {
+ $this->query = clone $this->query;
+ $this->request = clone $this->request;
+ $this->attributes = clone $this->attributes;
+ $this->cookies = clone $this->cookies;
+ $this->files = clone $this->files;
+ $this->server = clone $this->server;
+ $this->headers = clone $this->headers;
+ }
+
+ /**
+ * Returns the request as a string.
+ *
+ * @return string The request
+ */
+ public function __toString()
+ {
+ try {
+ $content = $this->getContent();
+ } catch (\LogicException $e) {
+ return trigger_error($e, E_USER_ERROR);
+ }
+
+ $cookieHeader = '';
+ $cookies = array();
+
+ foreach ($this->cookies as $k => $v) {
+ $cookies[] = $k.'='.$v;
+ }
+
+ if (!empty($cookies)) {
+ $cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n";
+ }
+
+ return
+ sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
+ $this->headers.
+ $cookieHeader."\r\n".
+ $content;
+ }
+
+ /**
+ * Overrides the PHP global variables according to this request instance.
+ *
+ * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE.
+ * $_FILES is never overridden, see rfc1867
+ */
+ public function overrideGlobals()
+ {
+ $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), '', '&')));
+
+ $_GET = $this->query->all();
+ $_POST = $this->request->all();
+ $_SERVER = $this->server->all();
+ $_COOKIE = $this->cookies->all();
+
+ foreach ($this->headers->all() as $key => $value) {
+ $key = strtoupper(str_replace('-', '_', $key));
+ if (\in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) {
+ $_SERVER[$key] = implode(', ', $value);
+ } else {
+ $_SERVER['HTTP_'.$key] = implode(', ', $value);
+ }
+ }
+
+ $request = array('g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE);
+
+ $requestOrder = ini_get('request_order') ?: ini_get('variables_order');
+ $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp';
+
+ $_REQUEST = array();
+ foreach (str_split($requestOrder) as $order) {
+ $_REQUEST = array_merge($_REQUEST, $request[$order]);
+ }
+ }
+
+ /**
+ * Sets a list of trusted proxies.
+ *
+ * You should only list the reverse proxies that you manage directly.
+ *
+ * @param array $proxies A list of trusted proxies
+ */
+ public static function setTrustedProxies(array $proxies)
+ {
+ self::$trustedProxies = $proxies;
+ }
+
+ /**
+ * Gets the list of trusted proxies.
+ *
+ * @return array An array of trusted proxies
+ */
+ public static function getTrustedProxies()
+ {
+ return self::$trustedProxies;
+ }
+
+ /**
+ * Sets a list of trusted host patterns.
+ *
+ * You should only list the hosts you manage using regexs.
+ *
+ * @param array $hostPatterns A list of trusted host patterns
+ */
+ public static function setTrustedHosts(array $hostPatterns)
+ {
+ self::$trustedHostPatterns = array_map(function ($hostPattern) {
+ return sprintf('{%s}i', $hostPattern);
+ }, $hostPatterns);
+ // we need to reset trusted hosts on trusted host patterns change
+ self::$trustedHosts = array();
+ }
+
+ /**
+ * Gets the list of trusted host patterns.
+ *
+ * @return array An array of trusted host patterns
+ */
+ public static function getTrustedHosts()
+ {
+ return self::$trustedHostPatterns;
+ }
+
+ /**
+ * Sets the name for trusted headers.
+ *
+ * The following header keys are supported:
+ *
+ * * Request::HEADER_CLIENT_IP: defaults to X-Forwarded-For (see getClientIp())
+ * * Request::HEADER_CLIENT_HOST: defaults to X-Forwarded-Host (see getHost())
+ * * Request::HEADER_CLIENT_PORT: defaults to X-Forwarded-Port (see getPort())
+ * * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure())
+ * * Request::HEADER_FORWARDED: defaults to Forwarded (see RFC 7239)
+ *
+ * Setting an empty value allows to disable the trusted header for the given key.
+ *
+ * @param string $key The header key
+ * @param string $value The header name
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function setTrustedHeaderName($key, $value)
+ {
+ if (!array_key_exists($key, self::$trustedHeaders)) {
+ throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key));
+ }
+
+ self::$trustedHeaders[$key] = $value;
+ }
+
+ /**
+ * Gets the trusted proxy header name.
+ *
+ * @param string $key The header key
+ *
+ * @return string The header name
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function getTrustedHeaderName($key)
+ {
+ if (!array_key_exists($key, self::$trustedHeaders)) {
+ throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key));
+ }
+
+ return self::$trustedHeaders[$key];
+ }
+
+ /**
+ * Normalizes a query string.
+ *
+ * It builds a normalized query string, where keys/value pairs are alphabetized,
+ * have consistent escaping and unneeded delimiters are removed.
+ *
+ * @param string $qs Query string
+ *
+ * @return string A normalized query string for the Request
+ */
+ public static function normalizeQueryString($qs)
+ {
+ if ('' == $qs) {
+ return '';
+ }
+
+ $parts = array();
+ $order = array();
+
+ foreach (explode('&', $qs) as $param) {
+ if ('' === $param || '=' === $param[0]) {
+ // Ignore useless delimiters, e.g. "x=y&".
+ // Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway.
+ // PHP also does not include them when building _GET.
+ continue;
+ }
+
+ $keyValuePair = explode('=', $param, 2);
+
+ // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded).
+ // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. This is why we use urldecode and then normalize to
+ // RFC 3986 with rawurlencode.
+ $parts[] = isset($keyValuePair[1]) ?
+ rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) :
+ rawurlencode(urldecode($keyValuePair[0]));
+ $order[] = urldecode($keyValuePair[0]);
+ }
+
+ array_multisort($order, SORT_ASC, $parts);
+
+ return implode('&', $parts);
+ }
+
+ /**
+ * Enables support for the _method request parameter to determine the intended HTTP method.
+ *
+ * Be warned that enabling this feature might lead to CSRF issues in your code.
+ * Check that you are using CSRF tokens when required.
+ * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered
+ * and used to send a "PUT" or "DELETE" request via the _method request parameter.
+ * If these methods are not protected against CSRF, this presents a possible vulnerability.
+ *
+ * The HTTP method can only be overridden when the real HTTP method is POST.
+ */
+ public static function enableHttpMethodParameterOverride()
+ {
+ self::$httpMethodParameterOverride = true;
+ }
+
+ /**
+ * Checks whether support for the _method request parameter is enabled.
+ *
+ * @return bool True when the _method request parameter is enabled, false otherwise
+ */
+ public static function getHttpMethodParameterOverride()
+ {
+ return self::$httpMethodParameterOverride;
+ }
+
+ /**
+ * Gets a "parameter" value.
+ *
+ * This method is mainly useful for libraries that want to provide some flexibility.
+ *
+ * Order of precedence: GET, PATH, POST
+ *
+ * Avoid using this method in controllers:
+ *
+ * * slow
+ * * prefer to get from a "named" source
+ *
+ * It is better to explicitly get request parameters from the appropriate
+ * public property instead (query, attributes, request).
+ *
+ * Note: Finding deep items is deprecated since version 2.8, to be removed in 3.0.
+ *
+ * @param string $key The key
+ * @param mixed $default The default value if the parameter key does not exist
+ * @param bool $deep Is parameter deep in multidimensional array
+ *
+ * @return mixed
+ */
+ public function get($key, $default = null, $deep = false)
+ {
+ if ($deep) {
+ @trigger_error('Using paths to find deeper items in '.__METHOD__.' is deprecated since Symfony 2.8 and will be removed in 3.0. Filter the returned value in your own code instead.', E_USER_DEPRECATED);
+ }
+
+ if ($this !== $result = $this->query->get($key, $this, $deep)) {
+ return $result;
+ }
+
+ if ($this !== $result = $this->attributes->get($key, $this, $deep)) {
+ return $result;
+ }
+
+ if ($this !== $result = $this->request->get($key, $this, $deep)) {
+ return $result;
+ }
+
+ return $default;
+ }
+
+ /**
+ * Gets the Session.
+ *
+ * @return SessionInterface|null The session
+ */
+ public function getSession()
+ {
+ return $this->session;
+ }
+
+ /**
+ * Whether the request contains a Session which was started in one of the
+ * previous requests.
+ *
+ * @return bool
+ */
+ public function hasPreviousSession()
+ {
+ // the check for $this->session avoids malicious users trying to fake a session cookie with proper name
+ return $this->hasSession() && $this->cookies->has($this->session->getName());
+ }
+
+ /**
+ * Whether the request contains a Session object.
+ *
+ * This method does not give any information about the state of the session object,
+ * like whether the session is started or not. It is just a way to check if this Request
+ * is associated with a Session instance.
+ *
+ * @return bool true when the Request contains a Session object, false otherwise
+ */
+ public function hasSession()
+ {
+ return null !== $this->session;
+ }
+
+ /**
+ * Sets the Session.
+ *
+ * @param SessionInterface $session The Session
+ */
+ public function setSession(SessionInterface $session)
+ {
+ $this->session = $session;
+ }
+
+ /**
+ * Returns the client IP addresses.
+ *
+ * In the returned array the most trusted IP address is first, and the
+ * least trusted one last. The "real" client IP address is the last one,
+ * but this is also the least trusted one. Trusted proxies are stripped.
+ *
+ * Use this method carefully; you should use getClientIp() instead.
+ *
+ * @return array The client IP addresses
+ *
+ * @see getClientIp()
+ */
+ public function getClientIps()
+ {
+ $ip = $this->server->get('REMOTE_ADDR');
+
+ if (!$this->isFromTrustedProxy()) {
+ return array($ip);
+ }
+
+ return $this->getTrustedValues(self::HEADER_CLIENT_IP, $ip) ?: array($ip);
+ }
+
+ /**
+ * Returns the client IP address.
+ *
+ * This method can read the client IP address from the "X-Forwarded-For" header
+ * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For"
+ * header value is a comma+space separated list of IP addresses, the left-most
+ * being the original client, and each successive proxy that passed the request
+ * adding the IP address where it received the request from.
+ *
+ * If your reverse proxy uses a different header name than "X-Forwarded-For",
+ * ("Client-Ip" for instance), configure it via "setTrustedHeaderName()" with
+ * the "client-ip" key.
+ *
+ * @return string|null The client IP address
+ *
+ * @see getClientIps()
+ * @see http://en.wikipedia.org/wiki/X-Forwarded-For
+ */
+ public function getClientIp()
+ {
+ $ipAddresses = $this->getClientIps();
+
+ return $ipAddresses[0];
+ }
+
+ /**
+ * Returns current script name.
+ *
+ * @return string
+ */
+ public function getScriptName()
+ {
+ return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', ''));
+ }
+
+ /**
+ * Returns the path being requested relative to the executed script.
+ *
+ * The path info always starts with a /.
+ *
+ * Suppose this request is instantiated from /mysite on localhost:
+ *
+ * * http://localhost/mysite returns an empty string
+ * * http://localhost/mysite/about returns '/about'
+ * * http://localhost/mysite/enco%20ded returns '/enco%20ded'
+ * * http://localhost/mysite/about?var=1 returns '/about'
+ *
+ * @return string The raw path (i.e. not urldecoded)
+ */
+ public function getPathInfo()
+ {
+ if (null === $this->pathInfo) {
+ $this->pathInfo = $this->preparePathInfo();
+ }
+
+ return $this->pathInfo;
+ }
+
+ /**
+ * Returns the root path from which this request is executed.
+ *
+ * Suppose that an index.php file instantiates this request object:
+ *
+ * * http://localhost/index.php returns an empty string
+ * * http://localhost/index.php/page returns an empty string
+ * * http://localhost/web/index.php returns '/web'
+ * * http://localhost/we%20b/index.php returns '/we%20b'
+ *
+ * @return string The raw path (i.e. not urldecoded)
+ */
+ public function getBasePath()
+ {
+ if (null === $this->basePath) {
+ $this->basePath = $this->prepareBasePath();
+ }
+
+ return $this->basePath;
+ }
+
+ /**
+ * Returns the root URL from which this request is executed.
+ *
+ * The base URL never ends with a /.
+ *
+ * This is similar to getBasePath(), except that it also includes the
+ * script filename (e.g. index.php) if one exists.
+ *
+ * @return string The raw URL (i.e. not urldecoded)
+ */
+ public function getBaseUrl()
+ {
+ if (null === $this->baseUrl) {
+ $this->baseUrl = $this->prepareBaseUrl();
+ }
+
+ return $this->baseUrl;
+ }
+
+ /**
+ * Gets the request's scheme.
+ *
+ * @return string
+ */
+ public function getScheme()
+ {
+ return $this->isSecure() ? 'https' : 'http';
+ }
+
+ /**
+ * Returns the port on which the request is made.
+ *
+ * This method can read the client port from the "X-Forwarded-Port" header
+ * when trusted proxies were set via "setTrustedProxies()".
+ *
+ * The "X-Forwarded-Port" header must contain the client port.
+ *
+ * If your reverse proxy uses a different header name than "X-Forwarded-Port",
+ * configure it via "setTrustedHeaderName()" with the "client-port" key.
+ *
+ * @return int|string can be a string if fetched from the server bag
+ */
+ public function getPort()
+ {
+ if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_PORT)) {
+ $host = $host[0];
+ } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) {
+ $host = $host[0];
+ } elseif (!$host = $this->headers->get('HOST')) {
+ return $this->server->get('SERVER_PORT');
+ }
+
+ if ('[' === $host[0]) {
+ $pos = strpos($host, ':', strrpos($host, ']'));
+ } else {
+ $pos = strrpos($host, ':');
+ }
+
+ if (false !== $pos) {
+ return (int) substr($host, $pos + 1);
+ }
+
+ return 'https' === $this->getScheme() ? 443 : 80;
+ }
+
+ /**
+ * Returns the user.
+ *
+ * @return string|null
+ */
+ public function getUser()
+ {
+ return $this->headers->get('PHP_AUTH_USER');
+ }
+
+ /**
+ * Returns the password.
+ *
+ * @return string|null
+ */
+ public function getPassword()
+ {
+ return $this->headers->get('PHP_AUTH_PW');
+ }
+
+ /**
+ * Gets the user info.
+ *
+ * @return string A user name and, optionally, scheme-specific information about how to gain authorization to access the server
+ */
+ public function getUserInfo()
+ {
+ $userinfo = $this->getUser();
+
+ $pass = $this->getPassword();
+ if ('' != $pass) {
+ $userinfo .= ":$pass";
+ }
+
+ return $userinfo;
+ }
+
+ /**
+ * Returns the HTTP host being requested.
+ *
+ * The port name will be appended to the host if it's non-standard.
+ *
+ * @return string
+ */
+ public function getHttpHost()
+ {
+ $scheme = $this->getScheme();
+ $port = $this->getPort();
+
+ if (('http' == $scheme && 80 == $port) || ('https' == $scheme && 443 == $port)) {
+ return $this->getHost();
+ }
+
+ return $this->getHost().':'.$port;
+ }
+
+ /**
+ * Returns the requested URI (path and query string).
+ *
+ * @return string The raw URI (i.e. not URI decoded)
+ */
+ public function getRequestUri()
+ {
+ if (null === $this->requestUri) {
+ $this->requestUri = $this->prepareRequestUri();
+ }
+
+ return $this->requestUri;
+ }
+
+ /**
+ * Gets the scheme and HTTP host.
+ *
+ * If the URL was called with basic authentication, the user
+ * and the password are not added to the generated string.
+ *
+ * @return string The scheme and HTTP host
+ */
+ public function getSchemeAndHttpHost()
+ {
+ return $this->getScheme().'://'.$this->getHttpHost();
+ }
+
+ /**
+ * Generates a normalized URI (URL) for the Request.
+ *
+ * @return string A normalized URI (URL) for the Request
+ *
+ * @see getQueryString()
+ */
+ public function getUri()
+ {
+ if (null !== $qs = $this->getQueryString()) {
+ $qs = '?'.$qs;
+ }
+
+ return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs;
+ }
+
+ /**
+ * Generates a normalized URI for the given path.
+ *
+ * @param string $path A path to use instead of the current one
+ *
+ * @return string The normalized URI for the path
+ */
+ public function getUriForPath($path)
+ {
+ return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path;
+ }
+
+ /**
+ * Returns the path as relative reference from the current Request path.
+ *
+ * Only the URIs path component (no schema, host etc.) is relevant and must be given.
+ * Both paths must be absolute and not contain relative parts.
+ * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives.
+ * Furthermore, they can be used to reduce the link size in documents.
+ *
+ * Example target paths, given a base path of "/a/b/c/d":
+ * - "/a/b/c/d" -> ""
+ * - "/a/b/c/" -> "./"
+ * - "/a/b/" -> "../"
+ * - "/a/b/c/other" -> "other"
+ * - "/a/x/y" -> "../../x/y"
+ *
+ * @param string $path The target path
+ *
+ * @return string The relative target path
+ */
+ public function getRelativeUriForPath($path)
+ {
+ // be sure that we are dealing with an absolute path
+ if (!isset($path[0]) || '/' !== $path[0]) {
+ return $path;
+ }
+
+ if ($path === $basePath = $this->getPathInfo()) {
+ return '';
+ }
+
+ $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath);
+ $targetDirs = explode('/', substr($path, 1));
+ array_pop($sourceDirs);
+ $targetFile = array_pop($targetDirs);
+
+ foreach ($sourceDirs as $i => $dir) {
+ if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) {
+ unset($sourceDirs[$i], $targetDirs[$i]);
+ } else {
+ break;
+ }
+ }
+
+ $targetDirs[] = $targetFile;
+ $path = str_repeat('../', \count($sourceDirs)).implode('/', $targetDirs);
+
+ // A reference to the same base directory or an empty subdirectory must be prefixed with "./".
+ // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
+ // as the first segment of a relative-path reference, as it would be mistaken for a scheme name
+ // (see http://tools.ietf.org/html/rfc3986#section-4.2).
+ return !isset($path[0]) || '/' === $path[0]
+ || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
+ ? "./$path" : $path;
+ }
+
+ /**
+ * Generates the normalized query string for the Request.
+ *
+ * It builds a normalized query string, where keys/value pairs are alphabetized
+ * and have consistent escaping.
+ *
+ * @return string|null A normalized query string for the Request
+ */
+ public function getQueryString()
+ {
+ $qs = static::normalizeQueryString($this->server->get('QUERY_STRING'));
+
+ return '' === $qs ? null : $qs;
+ }
+
+ /**
+ * Checks whether the request is secure or not.
+ *
+ * This method can read the client protocol from the "X-Forwarded-Proto" header
+ * when trusted proxies were set via "setTrustedProxies()".
+ *
+ * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http".
+ *
+ * If your reverse proxy uses a different header name than "X-Forwarded-Proto"
+ * ("SSL_HTTPS" for instance), configure it via "setTrustedHeaderName()" with
+ * the "client-proto" key.
+ *
+ * @return bool
+ */
+ public function isSecure()
+ {
+ if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_CLIENT_PROTO)) {
+ return \in_array(strtolower($proto[0]), array('https', 'on', 'ssl', '1'), true);
+ }
+
+ $https = $this->server->get('HTTPS');
+
+ return !empty($https) && 'off' !== strtolower($https);
+ }
+
+ /**
+ * Returns the host name.
+ *
+ * This method can read the client host name from the "X-Forwarded-Host" header
+ * when trusted proxies were set via "setTrustedProxies()".
+ *
+ * The "X-Forwarded-Host" header must contain the client host name.
+ *
+ * If your reverse proxy uses a different header name than "X-Forwarded-Host",
+ * configure it via "setTrustedHeaderName()" with the "client-host" key.
+ *
+ * @return string
+ *
+ * @throws \UnexpectedValueException when the host name is invalid
+ */
+ public function getHost()
+ {
+ if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) {
+ $host = $host[0];
+ } elseif (!$host = $this->headers->get('HOST')) {
+ if (!$host = $this->server->get('SERVER_NAME')) {
+ $host = $this->server->get('SERVER_ADDR', '');
+ }
+ }
+
+ // trim and remove port number from host
+ // host is lowercase as per RFC 952/2181
+ $host = strtolower(preg_replace('/:\d+$/', '', trim($host)));
+
+ // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
+ // check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
+ // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
+ if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
+ throw new \UnexpectedValueException(sprintf('Invalid Host "%s"', $host));
+ }
+
+ if (\count(self::$trustedHostPatterns) > 0) {
+ // to avoid host header injection attacks, you should provide a list of trusted host patterns
+
+ if (\in_array($host, self::$trustedHosts)) {
+ return $host;
+ }
+
+ foreach (self::$trustedHostPatterns as $pattern) {
+ if (preg_match($pattern, $host)) {
+ self::$trustedHosts[] = $host;
+
+ return $host;
+ }
+ }
+
+ throw new \UnexpectedValueException(sprintf('Untrusted Host "%s"', $host));
+ }
+
+ return $host;
+ }
+
+ /**
+ * Sets the request method.
+ *
+ * @param string $method
+ */
+ public function setMethod($method)
+ {
+ $this->method = null;
+ $this->server->set('REQUEST_METHOD', $method);
+ }
+
+ /**
+ * Gets the request "intended" method.
+ *
+ * If the X-HTTP-Method-Override header is set, and if the method is a POST,
+ * then it is used to determine the "real" intended HTTP method.
+ *
+ * The _method request parameter can also be used to determine the HTTP method,
+ * but only if enableHttpMethodParameterOverride() has been called.
+ *
+ * The method is always an uppercased string.
+ *
+ * @return string The request method
+ *
+ * @see getRealMethod()
+ */
+ public function getMethod()
+ {
+ if (null !== $this->method) {
+ return $this->method;
+ }
+
+ $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
+
+ if ('POST' !== $this->method) {
+ return $this->method;
+ }
+
+ $method = $this->headers->get('X-HTTP-METHOD-OVERRIDE');
+
+ if (!$method && self::$httpMethodParameterOverride) {
+ $method = $this->request->get('_method', $this->query->get('_method', 'POST'));
+ }
+
+ if (!\is_string($method)) {
+ return $this->method;
+ }
+
+ $method = strtoupper($method);
+
+ if (\in_array($method, array('GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'PATCH', 'PURGE', 'TRACE'), true)) {
+ return $this->method = $method;
+ }
+
+ if (!preg_match('/^[A-Z]++$/D', $method)) {
+ throw new \UnexpectedValueException(sprintf('Invalid method override "%s".', $method));
+ }
+
+ return $this->method = $method;
+ }
+
+ /**
+ * Gets the "real" request method.
+ *
+ * @return string The request method
+ *
+ * @see getMethod()
+ */
+ public function getRealMethod()
+ {
+ return strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
+ }
+
+ /**
+ * Gets the mime type associated with the format.
+ *
+ * @param string $format The format
+ *
+ * @return string|null The associated mime type (null if not found)
+ */
+ public function getMimeType($format)
+ {
+ if (null === static::$formats) {
+ static::initializeFormats();
+ }
+
+ return isset(static::$formats[$format]) ? static::$formats[$format][0] : null;
+ }
+
+ /**
+ * Gets the format associated with the mime type.
+ *
+ * @param string $mimeType The associated mime type
+ *
+ * @return string|null The format (null if not found)
+ */
+ public function getFormat($mimeType)
+ {
+ $canonicalMimeType = null;
+ if (false !== $pos = strpos($mimeType, ';')) {
+ $canonicalMimeType = trim(substr($mimeType, 0, $pos));
+ }
+
+ if (null === static::$formats) {
+ static::initializeFormats();
+ }
+
+ foreach (static::$formats as $format => $mimeTypes) {
+ if (\in_array($mimeType, (array) $mimeTypes)) {
+ return $format;
+ }
+ if (null !== $canonicalMimeType && \in_array($canonicalMimeType, (array) $mimeTypes)) {
+ return $format;
+ }
+ }
+ }
+
+ /**
+ * Associates a format with mime types.
+ *
+ * @param string $format The format
+ * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type)
+ */
+ public function setFormat($format, $mimeTypes)
+ {
+ if (null === static::$formats) {
+ static::initializeFormats();
+ }
+
+ static::$formats[$format] = \is_array($mimeTypes) ? $mimeTypes : array($mimeTypes);
+ }
+
+ /**
+ * Gets the request format.
+ *
+ * Here is the process to determine the format:
+ *
+ * * format defined by the user (with setRequestFormat())
+ * * _format request parameter
+ * * $default
+ *
+ * @param string|null $default The default format
+ *
+ * @return string The request format
+ */
+ public function getRequestFormat($default = 'html')
+ {
+ if (null === $this->format) {
+ $this->format = $this->get('_format');
+ }
+
+ return null === $this->format ? $default : $this->format;
+ }
+
+ /**
+ * Sets the request format.
+ *
+ * @param string $format The request format
+ */
+ public function setRequestFormat($format)
+ {
+ $this->format = $format;
+ }
+
+ /**
+ * Gets the format associated with the request.
+ *
+ * @return string|null The format (null if no content type is present)
+ */
+ public function getContentType()
+ {
+ return $this->getFormat($this->headers->get('CONTENT_TYPE'));
+ }
+
+ /**
+ * Sets the default locale.
+ *
+ * @param string $locale
+ */
+ public function setDefaultLocale($locale)
+ {
+ $this->defaultLocale = $locale;
+
+ if (null === $this->locale) {
+ $this->setPhpDefaultLocale($locale);
+ }
+ }
+
+ /**
+ * Get the default locale.
+ *
+ * @return string
+ */
+ public function getDefaultLocale()
+ {
+ return $this->defaultLocale;
+ }
+
+ /**
+ * Sets the locale.
+ *
+ * @param string $locale
+ */
+ public function setLocale($locale)
+ {
+ $this->setPhpDefaultLocale($this->locale = $locale);
+ }
+
+ /**
+ * Get the locale.
+ *
+ * @return string
+ */
+ public function getLocale()
+ {
+ return null === $this->locale ? $this->defaultLocale : $this->locale;
+ }
+
+ /**
+ * Checks if the request method is of specified type.
+ *
+ * @param string $method Uppercase request method (GET, POST etc)
+ *
+ * @return bool
+ */
+ public function isMethod($method)
+ {
+ return $this->getMethod() === strtoupper($method);
+ }
+
+ /**
+ * Checks whether the method is safe or not.
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-4.2.1
+ *
+ * @param bool $andCacheable Adds the additional condition that the method should be cacheable. True by default.
+ *
+ * @return bool
+ */
+ public function isMethodSafe(/* $andCacheable = true */)
+ {
+ return \in_array($this->getMethod(), 0 < \func_num_args() && !func_get_arg(0) ? array('GET', 'HEAD', 'OPTIONS', 'TRACE') : array('GET', 'HEAD'));
+ }
+
+ /**
+ * Checks whether the method is cacheable or not.
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-4.2.3
+ *
+ * @return bool True for GET and HEAD, false otherwise
+ */
+ public function isMethodCacheable()
+ {
+ return \in_array($this->getMethod(), array('GET', 'HEAD'));
+ }
+
+ /**
+ * Returns the request body content.
+ *
+ * @param bool $asResource If true, a resource will be returned
+ *
+ * @return string|resource The request body content or a resource to read the body stream
+ *
+ * @throws \LogicException
+ */
+ public function getContent($asResource = false)
+ {
+ $currentContentIsResource = \is_resource($this->content);
+ if (\PHP_VERSION_ID < 50600 && false === $this->content) {
+ throw new \LogicException('getContent() can only be called once when using the resource return type and PHP below 5.6.');
+ }
+
+ if (true === $asResource) {
+ if ($currentContentIsResource) {
+ rewind($this->content);
+
+ return $this->content;
+ }
+
+ // Content passed in parameter (test)
+ if (\is_string($this->content)) {
+ $resource = fopen('php://temp', 'r+');
+ fwrite($resource, $this->content);
+ rewind($resource);
+
+ return $resource;
+ }
+
+ $this->content = false;
+
+ return fopen('php://input', 'rb');
+ }
+
+ if ($currentContentIsResource) {
+ rewind($this->content);
+
+ return stream_get_contents($this->content);
+ }
+
+ if (null === $this->content || false === $this->content) {
+ $this->content = file_get_contents('php://input');
+ }
+
+ return $this->content;
+ }
+
+ /**
+ * Gets the Etags.
+ *
+ * @return array The entity tags
+ */
+ public function getETags()
+ {
+ return preg_split('/\s*,\s*/', $this->headers->get('if_none_match'), null, PREG_SPLIT_NO_EMPTY);
+ }
+
+ /**
+ * @return bool
+ */
+ public function isNoCache()
+ {
+ return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma');
+ }
+
+ /**
+ * Returns the preferred language.
+ *
+ * @param array $locales An array of ordered available locales
+ *
+ * @return string|null The preferred locale
+ */
+ public function getPreferredLanguage(array $locales = null)
+ {
+ $preferredLanguages = $this->getLanguages();
+
+ if (empty($locales)) {
+ return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null;
+ }
+
+ if (!$preferredLanguages) {
+ return $locales[0];
+ }
+
+ $extendedPreferredLanguages = array();
+ foreach ($preferredLanguages as $language) {
+ $extendedPreferredLanguages[] = $language;
+ if (false !== $position = strpos($language, '_')) {
+ $superLanguage = substr($language, 0, $position);
+ if (!\in_array($superLanguage, $preferredLanguages)) {
+ $extendedPreferredLanguages[] = $superLanguage;
+ }
+ }
+ }
+
+ $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales));
+
+ return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0];
+ }
+
+ /**
+ * Gets a list of languages acceptable by the client browser.
+ *
+ * @return array Languages ordered in the user browser preferences
+ */
+ public function getLanguages()
+ {
+ if (null !== $this->languages) {
+ return $this->languages;
+ }
+
+ $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all();
+ $this->languages = array();
+ foreach ($languages as $lang => $acceptHeaderItem) {
+ if (false !== strpos($lang, '-')) {
+ $codes = explode('-', $lang);
+ if ('i' === $codes[0]) {
+ // Language not listed in ISO 639 that are not variants
+ // of any listed language, which can be registered with the
+ // i-prefix, such as i-cherokee
+ if (\count($codes) > 1) {
+ $lang = $codes[1];
+ }
+ } else {
+ for ($i = 0, $max = \count($codes); $i < $max; ++$i) {
+ if (0 === $i) {
+ $lang = strtolower($codes[0]);
+ } else {
+ $lang .= '_'.strtoupper($codes[$i]);
+ }
+ }
+ }
+ }
+
+ $this->languages[] = $lang;
+ }
+
+ return $this->languages;
+ }
+
+ /**
+ * Gets a list of charsets acceptable by the client browser.
+ *
+ * @return array List of charsets in preferable order
+ */
+ public function getCharsets()
+ {
+ if (null !== $this->charsets) {
+ return $this->charsets;
+ }
+
+ return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all());
+ }
+
+ /**
+ * Gets a list of encodings acceptable by the client browser.
+ *
+ * @return array List of encodings in preferable order
+ */
+ public function getEncodings()
+ {
+ if (null !== $this->encodings) {
+ return $this->encodings;
+ }
+
+ return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all());
+ }
+
+ /**
+ * Gets a list of content types acceptable by the client browser.
+ *
+ * @return array List of content types in preferable order
+ */
+ public function getAcceptableContentTypes()
+ {
+ if (null !== $this->acceptableContentTypes) {
+ return $this->acceptableContentTypes;
+ }
+
+ return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all());
+ }
+
+ /**
+ * Returns true if the request is a XMLHttpRequest.
+ *
+ * It works if your JavaScript library sets an X-Requested-With HTTP header.
+ * It is known to work with common JavaScript frameworks:
+ *
+ * @see http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript
+ *
+ * @return bool true if the request is an XMLHttpRequest, false otherwise
+ */
+ public function isXmlHttpRequest()
+ {
+ return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
+ }
+
+ /*
+ * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24)
+ *
+ * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd).
+ *
+ * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ */
+
+ protected function prepareRequestUri()
+ {
+ $requestUri = '';
+
+ if ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) {
+ // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem)
+ $requestUri = $this->server->get('UNENCODED_URL');
+ $this->server->remove('UNENCODED_URL');
+ $this->server->remove('IIS_WasUrlRewritten');
+ } elseif ($this->server->has('REQUEST_URI')) {
+ $requestUri = $this->server->get('REQUEST_URI');
+ // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, only use URL path
+ $schemeAndHttpHost = $this->getSchemeAndHttpHost();
+ if (0 === strpos($requestUri, $schemeAndHttpHost)) {
+ $requestUri = substr($requestUri, \strlen($schemeAndHttpHost));
+ }
+ } elseif ($this->server->has('ORIG_PATH_INFO')) {
+ // IIS 5.0, PHP as CGI
+ $requestUri = $this->server->get('ORIG_PATH_INFO');
+ if ('' != $this->server->get('QUERY_STRING')) {
+ $requestUri .= '?'.$this->server->get('QUERY_STRING');
+ }
+ $this->server->remove('ORIG_PATH_INFO');
+ }
+
+ // normalize the request URI to ease creating sub-requests from this request
+ $this->server->set('REQUEST_URI', $requestUri);
+
+ return $requestUri;
+ }
+
+ /**
+ * Prepares the base URL.
+ *
+ * @return string
+ */
+ protected function prepareBaseUrl()
+ {
+ $filename = basename($this->server->get('SCRIPT_FILENAME'));
+
+ if (basename($this->server->get('SCRIPT_NAME')) === $filename) {
+ $baseUrl = $this->server->get('SCRIPT_NAME');
+ } elseif (basename($this->server->get('PHP_SELF')) === $filename) {
+ $baseUrl = $this->server->get('PHP_SELF');
+ } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) {
+ $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility
+ } else {
+ // Backtrack up the script_filename to find the portion matching
+ // php_self
+ $path = $this->server->get('PHP_SELF', '');
+ $file = $this->server->get('SCRIPT_FILENAME', '');
+ $segs = explode('/', trim($file, '/'));
+ $segs = array_reverse($segs);
+ $index = 0;
+ $last = \count($segs);
+ $baseUrl = '';
+ do {
+ $seg = $segs[$index];
+ $baseUrl = '/'.$seg.$baseUrl;
+ ++$index;
+ } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos);
+ }
+
+ // Does the baseUrl have anything in common with the request_uri?
+ $requestUri = $this->getRequestUri();
+ if ('' !== $requestUri && '/' !== $requestUri[0]) {
+ $requestUri = '/'.$requestUri;
+ }
+
+ if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) {
+ // full $baseUrl matches
+ return $prefix;
+ }
+
+ if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(\dirname($baseUrl), '/'.\DIRECTORY_SEPARATOR).'/')) {
+ // directory portion of $baseUrl matches
+ return rtrim($prefix, '/'.\DIRECTORY_SEPARATOR);
+ }
+
+ $truncatedRequestUri = $requestUri;
+ if (false !== $pos = strpos($requestUri, '?')) {
+ $truncatedRequestUri = substr($requestUri, 0, $pos);
+ }
+
+ $basename = basename($baseUrl);
+ if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) {
+ // no match whatsoever; set it blank
+ return '';
+ }
+
+ // If using mod_rewrite or ISAPI_Rewrite strip the script filename
+ // out of baseUrl. $pos !== 0 makes sure it is not matching a value
+ // from PATH_INFO or QUERY_STRING
+ if (\strlen($requestUri) >= \strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && 0 !== $pos) {
+ $baseUrl = substr($requestUri, 0, $pos + \strlen($baseUrl));
+ }
+
+ return rtrim($baseUrl, '/'.\DIRECTORY_SEPARATOR);
+ }
+
+ /**
+ * Prepares the base path.
+ *
+ * @return string base path
+ */
+ protected function prepareBasePath()
+ {
+ $filename = basename($this->server->get('SCRIPT_FILENAME'));
+ $baseUrl = $this->getBaseUrl();
+ if (empty($baseUrl)) {
+ return '';
+ }
+
+ if (basename($baseUrl) === $filename) {
+ $basePath = \dirname($baseUrl);
+ } else {
+ $basePath = $baseUrl;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $basePath = str_replace('\\', '/', $basePath);
+ }
+
+ return rtrim($basePath, '/');
+ }
+
+ /**
+ * Prepares the path info.
+ *
+ * @return string path info
+ */
+ protected function preparePathInfo()
+ {
+ $baseUrl = $this->getBaseUrl();
+
+ if (null === ($requestUri = $this->getRequestUri())) {
+ return '/';
+ }
+
+ // Remove the query string from REQUEST_URI
+ if (false !== $pos = strpos($requestUri, '?')) {
+ $requestUri = substr($requestUri, 0, $pos);
+ }
+ if ('' !== $requestUri && '/' !== $requestUri[0]) {
+ $requestUri = '/'.$requestUri;
+ }
+
+ $pathInfo = substr($requestUri, \strlen($baseUrl));
+ if (null !== $baseUrl && (false === $pathInfo || '' === $pathInfo)) {
+ // If substr() returns false then PATH_INFO is set to an empty string
+ return '/';
+ } elseif (null === $baseUrl) {
+ return $requestUri;
+ }
+
+ return (string) $pathInfo;
+ }
+
+ /**
+ * Initializes HTTP request formats.
+ */
+ protected static function initializeFormats()
+ {
+ static::$formats = array(
+ 'html' => array('text/html', 'application/xhtml+xml'),
+ 'txt' => array('text/plain'),
+ 'js' => array('application/javascript', 'application/x-javascript', 'text/javascript'),
+ 'css' => array('text/css'),
+ 'json' => array('application/json', 'application/x-json'),
+ 'jsonld' => array('application/ld+json'),
+ 'xml' => array('text/xml', 'application/xml', 'application/x-xml'),
+ 'rdf' => array('application/rdf+xml'),
+ 'atom' => array('application/atom+xml'),
+ 'rss' => array('application/rss+xml'),
+ 'form' => array('application/x-www-form-urlencoded'),
+ );
+ }
+
+ /**
+ * Sets the default PHP locale.
+ *
+ * @param string $locale
+ */
+ private function setPhpDefaultLocale($locale)
+ {
+ // if either the class Locale doesn't exist, or an exception is thrown when
+ // setting the default locale, the intl module is not installed, and
+ // the call can be ignored:
+ try {
+ if (class_exists('Locale', false)) {
+ \Locale::setDefault($locale);
+ }
+ } catch (\Exception $e) {
+ }
+ }
+
+ /*
+ * Returns the prefix as encoded in the string when the string starts with
+ * the given prefix, false otherwise.
+ *
+ * @param string $string The urlencoded string
+ * @param string $prefix The prefix not encoded
+ *
+ * @return string|false The prefix as it is encoded in $string, or false
+ */
+ private function getUrlencodedPrefix($string, $prefix)
+ {
+ if (0 !== strpos(rawurldecode($string), $prefix)) {
+ return false;
+ }
+
+ $len = \strlen($prefix);
+
+ if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) {
+ return $match[0];
+ }
+
+ return false;
+ }
+
+ private static function createRequestFromFactory(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
+ {
+ if (self::$requestFactory) {
+ $request = \call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content);
+
+ if (!$request instanceof self) {
+ throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.');
+ }
+
+ return $request;
+ }
+
+ return new static($query, $request, $attributes, $cookies, $files, $server, $content);
+ }
+
+ private function isFromTrustedProxy()
+ {
+ return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies);
+ }
+
+ private function getTrustedValues($type, $ip = null)
+ {
+ $clientValues = array();
+ $forwardedValues = array();
+
+ if (self::$trustedHeaders[$type] && $this->headers->has(self::$trustedHeaders[$type])) {
+ foreach (explode(',', $this->headers->get(self::$trustedHeaders[$type])) as $v) {
+ $clientValues[] = (self::HEADER_CLIENT_PORT === $type ? '0.0.0.0:' : '').trim($v);
+ }
+ }
+
+ if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
+ $forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
+ $forwardedValues = preg_match_all(sprintf('{(?:%s)="?([a-zA-Z0-9\.:_\-/\[\]]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array();
+ if (self::HEADER_CLIENT_PORT === $type) {
+ foreach ($forwardedValues as $k => $v) {
+ if (']' === substr($v, -1) || false === $v = strrchr($v, ':')) {
+ $v = $this->isSecure() ? ':443' : ':80';
+ }
+ $forwardedValues[$k] = '0.0.0.0'.$v;
+ }
+ }
+ }
+
+ if (null !== $ip) {
+ $clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip);
+ $forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip);
+ }
+
+ if ($forwardedValues === $clientValues || !$clientValues) {
+ return $forwardedValues;
+ }
+
+ if (!$forwardedValues) {
+ return $clientValues;
+ }
+
+ if (!$this->isForwardedValid) {
+ return null !== $ip ? array('0.0.0.0', $ip) : array();
+ }
+ $this->isForwardedValid = false;
+
+ throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::$trustedHeaders[self::HEADER_FORWARDED], self::$trustedHeaders[$type]));
+ }
+
+ private function normalizeAndFilterClientIps(array $clientIps, $ip)
+ {
+ if (!$clientIps) {
+ return array();
+ }
+ $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
+ $firstTrustedIp = null;
+
+ foreach ($clientIps as $key => $clientIp) {
+ if (strpos($clientIp, '.')) {
+ // Strip :port from IPv4 addresses. This is allowed in Forwarded
+ // and may occur in X-Forwarded-For.
+ $i = strpos($clientIp, ':');
+ if ($i) {
+ $clientIps[$key] = $clientIp = substr($clientIp, 0, $i);
+ }
+ } elseif (0 === strpos($clientIp, '[')) {
+ // Strip brackets and :port from IPv6 addresses.
+ $i = strpos($clientIp, ']', 1);
+ $clientIps[$key] = $clientIp = substr($clientIp, 1, $i - 1);
+ }
+
+ if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
+ unset($clientIps[$key]);
+
+ continue;
+ }
+
+ if (IpUtils::checkIp($clientIp, self::$trustedProxies)) {
+ unset($clientIps[$key]);
+
+ // Fallback to this when the client IP falls into the range of trusted proxies
+ if (null === $firstTrustedIp) {
+ $firstTrustedIp = $clientIp;
+ }
+ }
+ }
+
+ // Now the IP chain contains only untrusted proxies and the client IP
+ return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp);
+ }
+}
diff --git a/console/skel/symfony/http-foundation/RequestMatcher.php b/console/skel/symfony/http-foundation/RequestMatcher.php
new file mode 100644
index 0000000..6b4cef1
--- /dev/null
+++ b/console/skel/symfony/http-foundation/RequestMatcher.php
@@ -0,0 +1,178 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * RequestMatcher compares a pre-defined set of checks against a Request instance.
+ *
+ * @author Fabien Potencier
+ */
+class RequestMatcher implements RequestMatcherInterface
+{
+ /**
+ * @var string|null
+ */
+ private $path;
+
+ /**
+ * @var string|null
+ */
+ private $host;
+
+ /**
+ * @var string[]
+ */
+ private $methods = array();
+
+ /**
+ * @var string[]
+ */
+ private $ips = array();
+
+ /**
+ * @var array
+ */
+ private $attributes = array();
+
+ /**
+ * @var string[]
+ */
+ private $schemes = array();
+
+ /**
+ * @param string|null $path
+ * @param string|null $host
+ * @param string|string[]|null $methods
+ * @param string|string[]|null $ips
+ * @param array $attributes
+ * @param string|string[]|null $schemes
+ */
+ public function __construct($path = null, $host = null, $methods = null, $ips = null, array $attributes = array(), $schemes = null)
+ {
+ $this->matchPath($path);
+ $this->matchHost($host);
+ $this->matchMethod($methods);
+ $this->matchIps($ips);
+ $this->matchScheme($schemes);
+
+ foreach ($attributes as $k => $v) {
+ $this->matchAttribute($k, $v);
+ }
+ }
+
+ /**
+ * Adds a check for the HTTP scheme.
+ *
+ * @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes
+ */
+ public function matchScheme($scheme)
+ {
+ $this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : array();
+ }
+
+ /**
+ * Adds a check for the URL host name.
+ *
+ * @param string|null $regexp A Regexp
+ */
+ public function matchHost($regexp)
+ {
+ $this->host = $regexp;
+ }
+
+ /**
+ * Adds a check for the URL path info.
+ *
+ * @param string|null $regexp A Regexp
+ */
+ public function matchPath($regexp)
+ {
+ $this->path = $regexp;
+ }
+
+ /**
+ * Adds a check for the client IP.
+ *
+ * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
+ */
+ public function matchIp($ip)
+ {
+ $this->matchIps($ip);
+ }
+
+ /**
+ * Adds a check for the client IP.
+ *
+ * @param string|string[]|null $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
+ */
+ public function matchIps($ips)
+ {
+ $this->ips = null !== $ips ? (array) $ips : array();
+ }
+
+ /**
+ * Adds a check for the HTTP method.
+ *
+ * @param string|string[]|null $method An HTTP method or an array of HTTP methods
+ */
+ public function matchMethod($method)
+ {
+ $this->methods = null !== $method ? array_map('strtoupper', (array) $method) : array();
+ }
+
+ /**
+ * Adds a check for request attribute.
+ *
+ * @param string $key The request attribute name
+ * @param string $regexp A Regexp
+ */
+ public function matchAttribute($key, $regexp)
+ {
+ $this->attributes[$key] = $regexp;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function matches(Request $request)
+ {
+ if ($this->schemes && !\in_array($request->getScheme(), $this->schemes, true)) {
+ return false;
+ }
+
+ if ($this->methods && !\in_array($request->getMethod(), $this->methods, true)) {
+ return false;
+ }
+
+ foreach ($this->attributes as $key => $pattern) {
+ if (!preg_match('{'.$pattern.'}', $request->attributes->get($key))) {
+ return false;
+ }
+ }
+
+ if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getPathInfo()))) {
+ return false;
+ }
+
+ if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getHost())) {
+ return false;
+ }
+
+ if (IpUtils::checkIp($request->getClientIp(), $this->ips)) {
+ return true;
+ }
+
+ // Note to future implementors: add additional checks above the
+ // foreach above or else your check might not be run!
+ return 0 === \count($this->ips);
+ }
+}
diff --git a/console/skel/symfony/http-foundation/RequestMatcherInterface.php b/console/skel/symfony/http-foundation/RequestMatcherInterface.php
new file mode 100644
index 0000000..c26db3e
--- /dev/null
+++ b/console/skel/symfony/http-foundation/RequestMatcherInterface.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * RequestMatcherInterface is an interface for strategies to match a Request.
+ *
+ * @author Fabien Potencier
+ */
+interface RequestMatcherInterface
+{
+ /**
+ * Decides whether the rule(s) implemented by the strategy matches the supplied request.
+ *
+ * @return bool true if the request matches, false otherwise
+ */
+ public function matches(Request $request);
+}
diff --git a/console/skel/symfony/http-foundation/RequestStack.php b/console/skel/symfony/http-foundation/RequestStack.php
new file mode 100644
index 0000000..40123f6
--- /dev/null
+++ b/console/skel/symfony/http-foundation/RequestStack.php
@@ -0,0 +1,103 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Request stack that controls the lifecycle of requests.
+ *
+ * @author Benjamin Eberlei
+ */
+class RequestStack
+{
+ /**
+ * @var Request[]
+ */
+ private $requests = array();
+
+ /**
+ * Pushes a Request on the stack.
+ *
+ * This method should generally not be called directly as the stack
+ * management should be taken care of by the application itself.
+ */
+ public function push(Request $request)
+ {
+ $this->requests[] = $request;
+ }
+
+ /**
+ * Pops the current request from the stack.
+ *
+ * This operation lets the current request go out of scope.
+ *
+ * This method should generally not be called directly as the stack
+ * management should be taken care of by the application itself.
+ *
+ * @return Request|null
+ */
+ public function pop()
+ {
+ if (!$this->requests) {
+ return;
+ }
+
+ return array_pop($this->requests);
+ }
+
+ /**
+ * @return Request|null
+ */
+ public function getCurrentRequest()
+ {
+ return end($this->requests) ?: null;
+ }
+
+ /**
+ * Gets the master Request.
+ *
+ * Be warned that making your code aware of the master request
+ * might make it un-compatible with other features of your framework
+ * like ESI support.
+ *
+ * @return Request|null
+ */
+ public function getMasterRequest()
+ {
+ if (!$this->requests) {
+ return;
+ }
+
+ return $this->requests[0];
+ }
+
+ /**
+ * Returns the parent request of the current.
+ *
+ * Be warned that making your code aware of the parent request
+ * might make it un-compatible with other features of your framework
+ * like ESI support.
+ *
+ * If current Request is the master request, it returns null.
+ *
+ * @return Request|null
+ */
+ public function getParentRequest()
+ {
+ $pos = \count($this->requests) - 2;
+
+ if (!isset($this->requests[$pos])) {
+ return;
+ }
+
+ return $this->requests[$pos];
+ }
+}
diff --git a/console/skel/symfony/http-foundation/Response.php b/console/skel/symfony/http-foundation/Response.php
new file mode 100644
index 0000000..a4ad0e6
--- /dev/null
+++ b/console/skel/symfony/http-foundation/Response.php
@@ -0,0 +1,1198 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Response represents an HTTP response.
+ *
+ * @author Fabien Potencier
+ */
+class Response
+{
+ const HTTP_CONTINUE = 100;
+ const HTTP_SWITCHING_PROTOCOLS = 101;
+ const HTTP_PROCESSING = 102; // RFC2518
+ const HTTP_EARLY_HINTS = 103; // RFC8297
+ const HTTP_OK = 200;
+ const HTTP_CREATED = 201;
+ const HTTP_ACCEPTED = 202;
+ const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;
+ const HTTP_NO_CONTENT = 204;
+ const HTTP_RESET_CONTENT = 205;
+ const HTTP_PARTIAL_CONTENT = 206;
+ const HTTP_MULTI_STATUS = 207; // RFC4918
+ const HTTP_ALREADY_REPORTED = 208; // RFC5842
+ const HTTP_IM_USED = 226; // RFC3229
+ const HTTP_MULTIPLE_CHOICES = 300;
+ const HTTP_MOVED_PERMANENTLY = 301;
+ const HTTP_FOUND = 302;
+ const HTTP_SEE_OTHER = 303;
+ const HTTP_NOT_MODIFIED = 304;
+ const HTTP_USE_PROXY = 305;
+ const HTTP_RESERVED = 306;
+ const HTTP_TEMPORARY_REDIRECT = 307;
+ const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238
+ const HTTP_BAD_REQUEST = 400;
+ const HTTP_UNAUTHORIZED = 401;
+ const HTTP_PAYMENT_REQUIRED = 402;
+ const HTTP_FORBIDDEN = 403;
+ const HTTP_NOT_FOUND = 404;
+ const HTTP_METHOD_NOT_ALLOWED = 405;
+ const HTTP_NOT_ACCEPTABLE = 406;
+ const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
+ const HTTP_REQUEST_TIMEOUT = 408;
+ const HTTP_CONFLICT = 409;
+ const HTTP_GONE = 410;
+ const HTTP_LENGTH_REQUIRED = 411;
+ const HTTP_PRECONDITION_FAILED = 412;
+ const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
+ const HTTP_REQUEST_URI_TOO_LONG = 414;
+ const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
+ const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
+ const HTTP_EXPECTATION_FAILED = 417;
+ const HTTP_I_AM_A_TEAPOT = 418; // RFC2324
+ const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540
+ const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918
+ const HTTP_LOCKED = 423; // RFC4918
+ const HTTP_FAILED_DEPENDENCY = 424; // RFC4918
+
+ /**
+ * @deprecated
+ */
+ const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817
+ const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04
+ const HTTP_UPGRADE_REQUIRED = 426; // RFC2817
+ const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
+ const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585
+ const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585
+ const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451;
+ const HTTP_INTERNAL_SERVER_ERROR = 500;
+ const HTTP_NOT_IMPLEMENTED = 501;
+ const HTTP_BAD_GATEWAY = 502;
+ const HTTP_SERVICE_UNAVAILABLE = 503;
+ const HTTP_GATEWAY_TIMEOUT = 504;
+ const HTTP_VERSION_NOT_SUPPORTED = 505;
+ const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295
+ const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918
+ const HTTP_LOOP_DETECTED = 508; // RFC5842
+ const HTTP_NOT_EXTENDED = 510; // RFC2774
+ const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585
+
+ /**
+ * @var \Symfony\Component\HttpFoundation\ResponseHeaderBag
+ */
+ public $headers;
+
+ /**
+ * @var string
+ */
+ protected $content;
+
+ /**
+ * @var string
+ */
+ protected $version;
+
+ /**
+ * @var int
+ */
+ protected $statusCode;
+
+ /**
+ * @var string
+ */
+ protected $statusText;
+
+ /**
+ * @var string
+ */
+ protected $charset;
+
+ /**
+ * Status codes translation table.
+ *
+ * The list of codes is complete according to the
+ * {@link http://www.iana.org/assignments/http-status-codes/ Hypertext Transfer Protocol (HTTP) Status Code Registry}
+ * (last updated 2016-03-01).
+ *
+ * Unless otherwise noted, the status code is defined in RFC2616.
+ *
+ * @var array
+ */
+ public static $statusTexts = array(
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing', // RFC2518
+ 103 => 'Early Hints',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-Status', // RFC4918
+ 208 => 'Already Reported', // RFC5842
+ 226 => 'IM Used', // RFC3229
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 307 => 'Temporary Redirect',
+ 308 => 'Permanent Redirect', // RFC7238
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Payload Too Large',
+ 414 => 'URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot', // RFC2324
+ 421 => 'Misdirected Request', // RFC7540
+ 422 => 'Unprocessable Entity', // RFC4918
+ 423 => 'Locked', // RFC4918
+ 424 => 'Failed Dependency', // RFC4918
+ 425 => 'Too Early', // RFC-ietf-httpbis-replay-04
+ 426 => 'Upgrade Required', // RFC2817
+ 428 => 'Precondition Required', // RFC6585
+ 429 => 'Too Many Requests', // RFC6585
+ 431 => 'Request Header Fields Too Large', // RFC6585
+ 451 => 'Unavailable For Legal Reasons', // RFC7725
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 506 => 'Variant Also Negotiates', // RFC2295
+ 507 => 'Insufficient Storage', // RFC4918
+ 508 => 'Loop Detected', // RFC5842
+ 510 => 'Not Extended', // RFC2774
+ 511 => 'Network Authentication Required', // RFC6585
+ );
+
+ /**
+ * @param mixed $content The response content, see setContent()
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ *
+ * @throws \InvalidArgumentException When the HTTP status code is not valid
+ */
+ public function __construct($content = '', $status = 200, $headers = array())
+ {
+ $this->headers = new ResponseHeaderBag($headers);
+ $this->setContent($content);
+ $this->setStatusCode($status);
+ $this->setProtocolVersion('1.0');
+
+ /* RFC2616 - 14.18 says all Responses need to have a Date */
+ if (!$this->headers->has('Date')) {
+ $this->setDate(\DateTime::createFromFormat('U', time()));
+ }
+ }
+
+ /**
+ * Factory method for chainability.
+ *
+ * Example:
+ *
+ * return Response::create($body, 200)
+ * ->setSharedMaxAge(300);
+ *
+ * @param mixed $content The response content, see setContent()
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ *
+ * @return static
+ */
+ public static function create($content = '', $status = 200, $headers = array())
+ {
+ return new static($content, $status, $headers);
+ }
+
+ /**
+ * Returns the Response as an HTTP string.
+ *
+ * The string representation of the Response is the same as the
+ * one that will be sent to the client only if the prepare() method
+ * has been called before.
+ *
+ * @return string The Response as an HTTP string
+ *
+ * @see prepare()
+ */
+ public function __toString()
+ {
+ return
+ sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
+ $this->headers."\r\n".
+ $this->getContent();
+ }
+
+ /**
+ * Clones the current Response instance.
+ */
+ public function __clone()
+ {
+ $this->headers = clone $this->headers;
+ }
+
+ /**
+ * Prepares the Response before it is sent to the client.
+ *
+ * This method tweaks the Response to ensure that it is
+ * compliant with RFC 2616. Most of the changes are based on
+ * the Request that is "associated" with this Response.
+ *
+ * @return $this
+ */
+ public function prepare(Request $request)
+ {
+ $headers = $this->headers;
+
+ if ($this->isInformational() || $this->isEmpty()) {
+ $this->setContent(null);
+ $headers->remove('Content-Type');
+ $headers->remove('Content-Length');
+ } else {
+ // Content-type based on the Request
+ if (!$headers->has('Content-Type')) {
+ $format = $request->getRequestFormat();
+ if (null !== $format && $mimeType = $request->getMimeType($format)) {
+ $headers->set('Content-Type', $mimeType);
+ }
+ }
+
+ // Fix Content-Type
+ $charset = $this->charset ?: 'UTF-8';
+ if (!$headers->has('Content-Type')) {
+ $headers->set('Content-Type', 'text/html; charset='.$charset);
+ } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) {
+ // add the charset
+ $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset);
+ }
+
+ // Fix Content-Length
+ if ($headers->has('Transfer-Encoding')) {
+ $headers->remove('Content-Length');
+ }
+
+ if ($request->isMethod('HEAD')) {
+ // cf. RFC2616 14.13
+ $length = $headers->get('Content-Length');
+ $this->setContent(null);
+ if ($length) {
+ $headers->set('Content-Length', $length);
+ }
+ }
+ }
+
+ // Fix protocol
+ if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) {
+ $this->setProtocolVersion('1.1');
+ }
+
+ // Check if we need to send extra expire info headers
+ if ('1.0' == $this->getProtocolVersion() && 'no-cache' == $this->headers->get('Cache-Control')) {
+ $this->headers->set('pragma', 'no-cache');
+ $this->headers->set('expires', -1);
+ }
+
+ $this->ensureIEOverSSLCompatibility($request);
+
+ return $this;
+ }
+
+ /**
+ * Sends HTTP headers.
+ *
+ * @return $this
+ */
+ public function sendHeaders()
+ {
+ // headers have already been sent by the developer
+ if (headers_sent()) {
+ return $this;
+ }
+
+ /* RFC2616 - 14.18 says all Responses need to have a Date */
+ if (!$this->headers->has('Date')) {
+ $this->setDate(\DateTime::createFromFormat('U', time()));
+ }
+
+ // headers
+ foreach ($this->headers->allPreserveCase() as $name => $values) {
+ $replace = 0 === strcasecmp($name, 'Content-Type');
+ foreach ($values as $value) {
+ header($name.': '.$value, $replace, $this->statusCode);
+ }
+ }
+
+ // status
+ header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
+
+ // cookies
+ foreach ($this->headers->getCookies() as $cookie) {
+ setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sends content for the current web response.
+ *
+ * @return $this
+ */
+ public function sendContent()
+ {
+ echo $this->content;
+
+ return $this;
+ }
+
+ /**
+ * Sends HTTP headers and content.
+ *
+ * @return $this
+ */
+ public function send()
+ {
+ $this->sendHeaders();
+ $this->sendContent();
+
+ if (\function_exists('fastcgi_finish_request')) {
+ fastcgi_finish_request();
+ } elseif (!\in_array(\PHP_SAPI, array('cli', 'phpdbg'), true)) {
+ static::closeOutputBuffers(0, true);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the response content.
+ *
+ * Valid types are strings, numbers, null, and objects that implement a __toString() method.
+ *
+ * @param mixed $content Content that can be cast to string
+ *
+ * @return $this
+ *
+ * @throws \UnexpectedValueException
+ */
+ public function setContent($content)
+ {
+ if (null !== $content && !\is_string($content) && !is_numeric($content) && !\is_callable(array($content, '__toString'))) {
+ throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', \gettype($content)));
+ }
+
+ $this->content = (string) $content;
+
+ return $this;
+ }
+
+ /**
+ * Gets the current response content.
+ *
+ * @return string Content
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * Sets the HTTP protocol version (1.0 or 1.1).
+ *
+ * @param string $version The HTTP protocol version
+ *
+ * @return $this
+ */
+ public function setProtocolVersion($version)
+ {
+ $this->version = $version;
+
+ return $this;
+ }
+
+ /**
+ * Gets the HTTP protocol version.
+ *
+ * @return string The HTTP protocol version
+ */
+ public function getProtocolVersion()
+ {
+ return $this->version;
+ }
+
+ /**
+ * Sets the response status code.
+ *
+ * If the status text is null it will be automatically populated for the known
+ * status codes and left empty otherwise.
+ *
+ * @param int $code HTTP status code
+ * @param mixed $text HTTP status text
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException When the HTTP status code is not valid
+ */
+ public function setStatusCode($code, $text = null)
+ {
+ $this->statusCode = $code = (int) $code;
+ if ($this->isInvalid()) {
+ throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code));
+ }
+
+ if (null === $text) {
+ $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status';
+
+ return $this;
+ }
+
+ if (false === $text) {
+ $this->statusText = '';
+
+ return $this;
+ }
+
+ $this->statusText = $text;
+
+ return $this;
+ }
+
+ /**
+ * Retrieves the status code for the current web response.
+ *
+ * @return int Status code
+ */
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ /**
+ * Sets the response charset.
+ *
+ * @param string $charset Character set
+ *
+ * @return $this
+ */
+ public function setCharset($charset)
+ {
+ $this->charset = $charset;
+
+ return $this;
+ }
+
+ /**
+ * Retrieves the response charset.
+ *
+ * @return string Character set
+ */
+ public function getCharset()
+ {
+ return $this->charset;
+ }
+
+ /**
+ * Returns true if the response may safely be kept in a shared (surrogate) cache.
+ *
+ * Responses marked "private" with an explicit Cache-Control directive are
+ * considered uncacheable.
+ *
+ * Responses with neither a freshness lifetime (Expires, max-age) nor cache
+ * validator (Last-Modified, ETag) are considered uncacheable because there is
+ * no way to tell when or how to remove them from the cache.
+ *
+ * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation,
+ * for example "status codes that are defined as cacheable by default [...]
+ * can be reused by a cache with heuristic expiration unless otherwise indicated"
+ * (https://tools.ietf.org/html/rfc7231#section-6.1)
+ *
+ * @return bool true if the response is worth caching, false otherwise
+ */
+ public function isCacheable()
+ {
+ if (!\in_array($this->statusCode, array(200, 203, 300, 301, 302, 404, 410))) {
+ return false;
+ }
+
+ if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) {
+ return false;
+ }
+
+ return $this->isValidateable() || $this->isFresh();
+ }
+
+ /**
+ * Returns true if the response is "fresh".
+ *
+ * Fresh responses may be served from cache without any interaction with the
+ * origin. A response is considered fresh when it includes a Cache-Control/max-age
+ * indicator or Expires header and the calculated age is less than the freshness lifetime.
+ *
+ * @return bool true if the response is fresh, false otherwise
+ */
+ public function isFresh()
+ {
+ return $this->getTtl() > 0;
+ }
+
+ /**
+ * Returns true if the response includes headers that can be used to validate
+ * the response with the origin server using a conditional GET request.
+ *
+ * @return bool true if the response is validateable, false otherwise
+ */
+ public function isValidateable()
+ {
+ return $this->headers->has('Last-Modified') || $this->headers->has('ETag');
+ }
+
+ /**
+ * Marks the response as "private".
+ *
+ * It makes the response ineligible for serving other clients.
+ *
+ * @return $this
+ */
+ public function setPrivate()
+ {
+ $this->headers->removeCacheControlDirective('public');
+ $this->headers->addCacheControlDirective('private');
+
+ return $this;
+ }
+
+ /**
+ * Marks the response as "public".
+ *
+ * It makes the response eligible for serving other clients.
+ *
+ * @return $this
+ */
+ public function setPublic()
+ {
+ $this->headers->addCacheControlDirective('public');
+ $this->headers->removeCacheControlDirective('private');
+
+ return $this;
+ }
+
+ /**
+ * Returns true if the response must be revalidated by caches.
+ *
+ * This method indicates that the response must not be served stale by a
+ * cache in any circumstance without first revalidating with the origin.
+ * When present, the TTL of the response should not be overridden to be
+ * greater than the value provided by the origin.
+ *
+ * @return bool true if the response must be revalidated by a cache, false otherwise
+ */
+ public function mustRevalidate()
+ {
+ return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate');
+ }
+
+ /**
+ * Returns the Date header as a DateTime instance.
+ *
+ * @return \DateTime A \DateTime instance
+ *
+ * @throws \RuntimeException When the header is not parseable
+ */
+ public function getDate()
+ {
+ /*
+ RFC2616 - 14.18 says all Responses need to have a Date.
+ Make sure we provide one even if it the header
+ has been removed in the meantime.
+ */
+ if (!$this->headers->has('Date')) {
+ $this->setDate(\DateTime::createFromFormat('U', time()));
+ }
+
+ return $this->headers->getDate('Date');
+ }
+
+ /**
+ * Sets the Date header.
+ *
+ * @return $this
+ */
+ public function setDate(\DateTime $date)
+ {
+ $date->setTimezone(new \DateTimeZone('UTC'));
+ $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT');
+
+ return $this;
+ }
+
+ /**
+ * Returns the age of the response.
+ *
+ * @return int The age of the response in seconds
+ */
+ public function getAge()
+ {
+ if (null !== $age = $this->headers->get('Age')) {
+ return (int) $age;
+ }
+
+ return max(time() - $this->getDate()->format('U'), 0);
+ }
+
+ /**
+ * Marks the response stale by setting the Age header to be equal to the maximum age of the response.
+ *
+ * @return $this
+ */
+ public function expire()
+ {
+ if ($this->isFresh()) {
+ $this->headers->set('Age', $this->getMaxAge());
+ $this->headers->remove('Expires');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the value of the Expires header as a DateTime instance.
+ *
+ * @return \DateTime|null A DateTime instance or null if the header does not exist
+ */
+ public function getExpires()
+ {
+ try {
+ return $this->headers->getDate('Expires');
+ } catch (\RuntimeException $e) {
+ // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past
+ return \DateTime::createFromFormat(DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000');
+ }
+ }
+
+ /**
+ * Sets the Expires HTTP header with a DateTime instance.
+ *
+ * Passing null as value will remove the header.
+ *
+ * @param \DateTime|null $date A \DateTime instance or null to remove the header
+ *
+ * @return $this
+ */
+ public function setExpires(\DateTime $date = null)
+ {
+ if (null === $date) {
+ $this->headers->remove('Expires');
+ } else {
+ $date = clone $date;
+ $date->setTimezone(new \DateTimeZone('UTC'));
+ $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the number of seconds after the time specified in the response's Date
+ * header when the response should no longer be considered fresh.
+ *
+ * First, it checks for a s-maxage directive, then a max-age directive, and then it falls
+ * back on an expires header. It returns null when no maximum age can be established.
+ *
+ * @return int|null Number of seconds
+ */
+ public function getMaxAge()
+ {
+ if ($this->headers->hasCacheControlDirective('s-maxage')) {
+ return (int) $this->headers->getCacheControlDirective('s-maxage');
+ }
+
+ if ($this->headers->hasCacheControlDirective('max-age')) {
+ return (int) $this->headers->getCacheControlDirective('max-age');
+ }
+
+ if (null !== $this->getExpires()) {
+ return $this->getExpires()->format('U') - $this->getDate()->format('U');
+ }
+ }
+
+ /**
+ * Sets the number of seconds after which the response should no longer be considered fresh.
+ *
+ * This methods sets the Cache-Control max-age directive.
+ *
+ * @param int $value Number of seconds
+ *
+ * @return $this
+ */
+ public function setMaxAge($value)
+ {
+ $this->headers->addCacheControlDirective('max-age', $value);
+
+ return $this;
+ }
+
+ /**
+ * Sets the number of seconds after which the response should no longer be considered fresh by shared caches.
+ *
+ * This methods sets the Cache-Control s-maxage directive.
+ *
+ * @param int $value Number of seconds
+ *
+ * @return $this
+ */
+ public function setSharedMaxAge($value)
+ {
+ $this->setPublic();
+ $this->headers->addCacheControlDirective('s-maxage', $value);
+
+ return $this;
+ }
+
+ /**
+ * Returns the response's time-to-live in seconds.
+ *
+ * It returns null when no freshness information is present in the response.
+ *
+ * When the responses TTL is <= 0, the response may not be served from cache without first
+ * revalidating with the origin.
+ *
+ * @return int|null The TTL in seconds
+ */
+ public function getTtl()
+ {
+ if (null !== $maxAge = $this->getMaxAge()) {
+ return $maxAge - $this->getAge();
+ }
+ }
+
+ /**
+ * Sets the response's time-to-live for shared caches.
+ *
+ * This method adjusts the Cache-Control/s-maxage directive.
+ *
+ * @param int $seconds Number of seconds
+ *
+ * @return $this
+ */
+ public function setTtl($seconds)
+ {
+ $this->setSharedMaxAge($this->getAge() + $seconds);
+
+ return $this;
+ }
+
+ /**
+ * Sets the response's time-to-live for private/client caches.
+ *
+ * This method adjusts the Cache-Control/max-age directive.
+ *
+ * @param int $seconds Number of seconds
+ *
+ * @return $this
+ */
+ public function setClientTtl($seconds)
+ {
+ $this->setMaxAge($this->getAge() + $seconds);
+
+ return $this;
+ }
+
+ /**
+ * Returns the Last-Modified HTTP header as a DateTime instance.
+ *
+ * @return \DateTime|null A DateTime instance or null if the header does not exist
+ *
+ * @throws \RuntimeException When the HTTP header is not parseable
+ */
+ public function getLastModified()
+ {
+ return $this->headers->getDate('Last-Modified');
+ }
+
+ /**
+ * Sets the Last-Modified HTTP header with a DateTime instance.
+ *
+ * Passing null as value will remove the header.
+ *
+ * @param \DateTime|null $date A \DateTime instance or null to remove the header
+ *
+ * @return $this
+ */
+ public function setLastModified(\DateTime $date = null)
+ {
+ if (null === $date) {
+ $this->headers->remove('Last-Modified');
+ } else {
+ $date = clone $date;
+ $date->setTimezone(new \DateTimeZone('UTC'));
+ $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the literal value of the ETag HTTP header.
+ *
+ * @return string|null The ETag HTTP header or null if it does not exist
+ */
+ public function getEtag()
+ {
+ return $this->headers->get('ETag');
+ }
+
+ /**
+ * Sets the ETag value.
+ *
+ * @param string|null $etag The ETag unique identifier or null to remove the header
+ * @param bool $weak Whether you want a weak ETag or not
+ *
+ * @return $this
+ */
+ public function setEtag($etag = null, $weak = false)
+ {
+ if (null === $etag) {
+ $this->headers->remove('Etag');
+ } else {
+ if (0 !== strpos($etag, '"')) {
+ $etag = '"'.$etag.'"';
+ }
+
+ $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the response's cache headers (validation and/or expiration).
+ *
+ * Available options are: etag, last_modified, max_age, s_maxage, private, and public.
+ *
+ * @param array $options An array of cache options
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setCache(array $options)
+ {
+ if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public'))) {
+ throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', array_values($diff))));
+ }
+
+ if (isset($options['etag'])) {
+ $this->setEtag($options['etag']);
+ }
+
+ if (isset($options['last_modified'])) {
+ $this->setLastModified($options['last_modified']);
+ }
+
+ if (isset($options['max_age'])) {
+ $this->setMaxAge($options['max_age']);
+ }
+
+ if (isset($options['s_maxage'])) {
+ $this->setSharedMaxAge($options['s_maxage']);
+ }
+
+ if (isset($options['public'])) {
+ if ($options['public']) {
+ $this->setPublic();
+ } else {
+ $this->setPrivate();
+ }
+ }
+
+ if (isset($options['private'])) {
+ if ($options['private']) {
+ $this->setPrivate();
+ } else {
+ $this->setPublic();
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Modifies the response so that it conforms to the rules defined for a 304 status code.
+ *
+ * This sets the status, removes the body, and discards any headers
+ * that MUST NOT be included in 304 responses.
+ *
+ * @return $this
+ *
+ * @see http://tools.ietf.org/html/rfc2616#section-10.3.5
+ */
+ public function setNotModified()
+ {
+ $this->setStatusCode(304);
+ $this->setContent(null);
+
+ // remove headers that MUST NOT be included with 304 Not Modified responses
+ foreach (array('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified') as $header) {
+ $this->headers->remove($header);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns true if the response includes a Vary header.
+ *
+ * @return bool true if the response includes a Vary header, false otherwise
+ */
+ public function hasVary()
+ {
+ return null !== $this->headers->get('Vary');
+ }
+
+ /**
+ * Returns an array of header names given in the Vary header.
+ *
+ * @return array An array of Vary names
+ */
+ public function getVary()
+ {
+ if (!$vary = $this->headers->get('Vary', null, false)) {
+ return array();
+ }
+
+ $ret = array();
+ foreach ($vary as $item) {
+ $ret = array_merge($ret, preg_split('/[\s,]+/', $item));
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Sets the Vary header.
+ *
+ * @param string|array $headers
+ * @param bool $replace Whether to replace the actual value or not (true by default)
+ *
+ * @return $this
+ */
+ public function setVary($headers, $replace = true)
+ {
+ $this->headers->set('Vary', $headers, $replace);
+
+ return $this;
+ }
+
+ /**
+ * Determines if the Response validators (ETag, Last-Modified) match
+ * a conditional value specified in the Request.
+ *
+ * If the Response is not modified, it sets the status code to 304 and
+ * removes the actual content by calling the setNotModified() method.
+ *
+ * @return bool true if the Response validators match the Request, false otherwise
+ */
+ public function isNotModified(Request $request)
+ {
+ if (!$request->isMethodCacheable()) {
+ return false;
+ }
+
+ $notModified = false;
+ $lastModified = $this->headers->get('Last-Modified');
+ $modifiedSince = $request->headers->get('If-Modified-Since');
+
+ if ($etags = $request->getETags()) {
+ $notModified = \in_array($this->getEtag(), $etags) || \in_array('*', $etags);
+ }
+
+ if ($modifiedSince && $lastModified) {
+ $notModified = strtotime($modifiedSince) >= strtotime($lastModified) && (!$etags || $notModified);
+ }
+
+ if ($notModified) {
+ $this->setNotModified();
+ }
+
+ return $notModified;
+ }
+
+ /**
+ * Is response invalid?
+ *
+ * @return bool
+ *
+ * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+ */
+ public function isInvalid()
+ {
+ return $this->statusCode < 100 || $this->statusCode >= 600;
+ }
+
+ /**
+ * Is response informative?
+ *
+ * @return bool
+ */
+ public function isInformational()
+ {
+ return $this->statusCode >= 100 && $this->statusCode < 200;
+ }
+
+ /**
+ * Is response successful?
+ *
+ * @return bool
+ */
+ public function isSuccessful()
+ {
+ return $this->statusCode >= 200 && $this->statusCode < 300;
+ }
+
+ /**
+ * Is the response a redirect?
+ *
+ * @return bool
+ */
+ public function isRedirection()
+ {
+ return $this->statusCode >= 300 && $this->statusCode < 400;
+ }
+
+ /**
+ * Is there a client error?
+ *
+ * @return bool
+ */
+ public function isClientError()
+ {
+ return $this->statusCode >= 400 && $this->statusCode < 500;
+ }
+
+ /**
+ * Was there a server side error?
+ *
+ * @return bool
+ */
+ public function isServerError()
+ {
+ return $this->statusCode >= 500 && $this->statusCode < 600;
+ }
+
+ /**
+ * Is the response OK?
+ *
+ * @return bool
+ */
+ public function isOk()
+ {
+ return 200 === $this->statusCode;
+ }
+
+ /**
+ * Is the response forbidden?
+ *
+ * @return bool
+ */
+ public function isForbidden()
+ {
+ return 403 === $this->statusCode;
+ }
+
+ /**
+ * Is the response a not found error?
+ *
+ * @return bool
+ */
+ public function isNotFound()
+ {
+ return 404 === $this->statusCode;
+ }
+
+ /**
+ * Is the response a redirect of some form?
+ *
+ * @param string $location
+ *
+ * @return bool
+ */
+ public function isRedirect($location = null)
+ {
+ return \in_array($this->statusCode, array(201, 301, 302, 303, 307, 308)) && (null === $location ?: $location == $this->headers->get('Location'));
+ }
+
+ /**
+ * Is the response empty?
+ *
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return \in_array($this->statusCode, array(204, 304));
+ }
+
+ /**
+ * Cleans or flushes output buffers up to target level.
+ *
+ * Resulting level can be greater than target level if a non-removable buffer has been encountered.
+ *
+ * @param int $targetLevel The target output buffering level
+ * @param bool $flush Whether to flush or clean the buffers
+ */
+ public static function closeOutputBuffers($targetLevel, $flush)
+ {
+ $status = ob_get_status(true);
+ $level = \count($status);
+ $flags = \defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE) : -1;
+
+ while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) {
+ if ($flush) {
+ ob_end_flush();
+ } else {
+ ob_end_clean();
+ }
+ }
+ }
+
+ /**
+ * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9.
+ *
+ * @see http://support.microsoft.com/kb/323308
+ */
+ protected function ensureIEOverSSLCompatibility(Request $request)
+ {
+ if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) && true === $request->isSecure()) {
+ if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) {
+ $this->headers->remove('Cache-Control');
+ }
+ }
+ }
+}
diff --git a/console/skel/symfony/http-foundation/ResponseHeaderBag.php b/console/skel/symfony/http-foundation/ResponseHeaderBag.php
new file mode 100644
index 0000000..e97eefc
--- /dev/null
+++ b/console/skel/symfony/http-foundation/ResponseHeaderBag.php
@@ -0,0 +1,283 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * ResponseHeaderBag is a container for Response HTTP headers.
+ *
+ * @author Fabien Potencier
+ */
+class ResponseHeaderBag extends HeaderBag
+{
+ const COOKIES_FLAT = 'flat';
+ const COOKIES_ARRAY = 'array';
+
+ const DISPOSITION_ATTACHMENT = 'attachment';
+ const DISPOSITION_INLINE = 'inline';
+
+ protected $computedCacheControl = array();
+ protected $cookies = array();
+ protected $headerNames = array();
+
+ public function __construct(array $headers = array())
+ {
+ parent::__construct($headers);
+
+ if (!isset($this->headers['cache-control'])) {
+ $this->set('Cache-Control', '');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __toString()
+ {
+ $cookies = '';
+ foreach ($this->getCookies() as $cookie) {
+ $cookies .= 'Set-Cookie: '.$cookie."\r\n";
+ }
+
+ ksort($this->headerNames);
+
+ return parent::__toString().$cookies;
+ }
+
+ /**
+ * Returns the headers, with original capitalizations.
+ *
+ * @return array An array of headers
+ */
+ public function allPreserveCase()
+ {
+ return array_combine($this->headerNames, $this->headers);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function replace(array $headers = array())
+ {
+ $this->headerNames = array();
+
+ parent::replace($headers);
+
+ if (!isset($this->headers['cache-control'])) {
+ $this->set('Cache-Control', '');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($key, $values, $replace = true)
+ {
+ parent::set($key, $values, $replace);
+
+ $uniqueKey = str_replace('_', '-', strtolower($key));
+ $this->headerNames[$uniqueKey] = $key;
+
+ // ensure the cache-control header has sensible defaults
+ if (\in_array($uniqueKey, array('cache-control', 'etag', 'last-modified', 'expires'))) {
+ $computed = $this->computeCacheControlValue();
+ $this->headers['cache-control'] = array($computed);
+ $this->headerNames['cache-control'] = 'Cache-Control';
+ $this->computedCacheControl = $this->parseCacheControl($computed);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function remove($key)
+ {
+ parent::remove($key);
+
+ $uniqueKey = str_replace('_', '-', strtolower($key));
+ unset($this->headerNames[$uniqueKey]);
+
+ if ('cache-control' === $uniqueKey) {
+ $this->computedCacheControl = array();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasCacheControlDirective($key)
+ {
+ return array_key_exists($key, $this->computedCacheControl);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheControlDirective($key)
+ {
+ return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null;
+ }
+
+ public function setCookie(Cookie $cookie)
+ {
+ $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
+ }
+
+ /**
+ * Removes a cookie from the array, but does not unset it in the browser.
+ *
+ * @param string $name
+ * @param string $path
+ * @param string $domain
+ */
+ public function removeCookie($name, $path = '/', $domain = null)
+ {
+ if (null === $path) {
+ $path = '/';
+ }
+
+ unset($this->cookies[$domain][$path][$name]);
+
+ if (empty($this->cookies[$domain][$path])) {
+ unset($this->cookies[$domain][$path]);
+
+ if (empty($this->cookies[$domain])) {
+ unset($this->cookies[$domain]);
+ }
+ }
+ }
+
+ /**
+ * Returns an array with all cookies.
+ *
+ * @param string $format
+ *
+ * @return Cookie[]
+ *
+ * @throws \InvalidArgumentException When the $format is invalid
+ */
+ public function getCookies($format = self::COOKIES_FLAT)
+ {
+ if (!\in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) {
+ throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY))));
+ }
+
+ if (self::COOKIES_ARRAY === $format) {
+ return $this->cookies;
+ }
+
+ $flattenedCookies = array();
+ foreach ($this->cookies as $path) {
+ foreach ($path as $cookies) {
+ foreach ($cookies as $cookie) {
+ $flattenedCookies[] = $cookie;
+ }
+ }
+ }
+
+ return $flattenedCookies;
+ }
+
+ /**
+ * Clears a cookie in the browser.
+ *
+ * @param string $name
+ * @param string $path
+ * @param string $domain
+ * @param bool $secure
+ * @param bool $httpOnly
+ */
+ public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true)
+ {
+ $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly));
+ }
+
+ /**
+ * Generates a HTTP Content-Disposition field-value.
+ *
+ * @param string $disposition One of "inline" or "attachment"
+ * @param string $filename A unicode string
+ * @param string $filenameFallback A string containing only ASCII characters that
+ * is semantically equivalent to $filename. If the filename is already ASCII,
+ * it can be omitted, or just copied from $filename
+ *
+ * @return string A string suitable for use as a Content-Disposition field-value
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @see RFC 6266
+ */
+ public function makeDisposition($disposition, $filename, $filenameFallback = '')
+ {
+ if (!\in_array($disposition, array(self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE))) {
+ throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
+ }
+
+ if ('' == $filenameFallback) {
+ $filenameFallback = $filename;
+ }
+
+ // filenameFallback is not ASCII.
+ if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) {
+ throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.');
+ }
+
+ // percent characters aren't safe in fallback.
+ if (false !== strpos($filenameFallback, '%')) {
+ throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');
+ }
+
+ // path separators aren't allowed in either.
+ if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) {
+ throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');
+ }
+
+ $output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback));
+
+ if ($filename !== $filenameFallback) {
+ $output .= sprintf("; filename*=utf-8''%s", rawurlencode($filename));
+ }
+
+ return $output;
+ }
+
+ /**
+ * Returns the calculated value of the cache-control header.
+ *
+ * This considers several other headers and calculates or modifies the
+ * cache-control header to a sensible, conservative value.
+ *
+ * @return string
+ */
+ protected function computeCacheControlValue()
+ {
+ if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) {
+ return 'no-cache';
+ }
+
+ if (!$this->cacheControl) {
+ // conservative by default
+ return 'private, must-revalidate';
+ }
+
+ $header = $this->getCacheControlHeader();
+ if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) {
+ return $header;
+ }
+
+ // public if s-maxage is defined, private otherwise
+ if (!isset($this->cacheControl['s-maxage'])) {
+ return $header.', private';
+ }
+
+ return $header;
+ }
+}
diff --git a/console/skel/symfony/http-foundation/ServerBag.php b/console/skel/symfony/http-foundation/ServerBag.php
new file mode 100644
index 0000000..d8ab561
--- /dev/null
+++ b/console/skel/symfony/http-foundation/ServerBag.php
@@ -0,0 +1,102 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * ServerBag is a container for HTTP headers from the $_SERVER variable.
+ *
+ * @author Fabien Potencier
+ * @author Bulat Shakirzyanov
+ * @author Robert Kiss
+ */
+class ServerBag extends ParameterBag
+{
+ /**
+ * Gets the HTTP headers.
+ *
+ * @return array
+ */
+ public function getHeaders()
+ {
+ $headers = array();
+ $contentHeaders = array('CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true);
+ foreach ($this->parameters as $key => $value) {
+ if (0 === strpos($key, 'HTTP_')) {
+ $headers[substr($key, 5)] = $value;
+ }
+ // CONTENT_* are not prefixed with HTTP_
+ elseif (isset($contentHeaders[$key])) {
+ $headers[$key] = $value;
+ }
+ }
+
+ if (isset($this->parameters['PHP_AUTH_USER'])) {
+ $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER'];
+ $headers['PHP_AUTH_PW'] = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : '';
+ } else {
+ /*
+ * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default
+ * For this workaround to work, add these lines to your .htaccess file:
+ * RewriteCond %{HTTP:Authorization} ^(.+)$
+ * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+ *
+ * A sample .htaccess file:
+ * RewriteEngine On
+ * RewriteCond %{HTTP:Authorization} ^(.+)$
+ * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+ * RewriteCond %{REQUEST_FILENAME} !-f
+ * RewriteRule ^(.*)$ app.php [QSA,L]
+ */
+
+ $authorizationHeader = null;
+ if (isset($this->parameters['HTTP_AUTHORIZATION'])) {
+ $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION'];
+ } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) {
+ $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION'];
+ }
+
+ if (null !== $authorizationHeader) {
+ if (0 === stripos($authorizationHeader, 'basic ')) {
+ // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic
+ $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2);
+ if (2 == \count($exploded)) {
+ list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded;
+ }
+ } elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) {
+ // In some circumstances PHP_AUTH_DIGEST needs to be set
+ $headers['PHP_AUTH_DIGEST'] = $authorizationHeader;
+ $this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader;
+ } elseif (0 === stripos($authorizationHeader, 'bearer ')) {
+ /*
+ * XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables,
+ * I'll just set $headers['AUTHORIZATION'] here.
+ * http://php.net/manual/en/reserved.variables.server.php
+ */
+ $headers['AUTHORIZATION'] = $authorizationHeader;
+ }
+ }
+ }
+
+ if (isset($headers['AUTHORIZATION'])) {
+ return $headers;
+ }
+
+ // PHP_AUTH_USER/PHP_AUTH_PW
+ if (isset($headers['PHP_AUTH_USER'])) {
+ $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']);
+ } elseif (isset($headers['PHP_AUTH_DIGEST'])) {
+ $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST'];
+ }
+
+ return $headers;
+ }
+}
diff --git a/console/skel/symfony/http-foundation/Session/Attribute/AttributeBag.php b/console/skel/symfony/http-foundation/Session/Attribute/AttributeBag.php
new file mode 100644
index 0000000..fc5fb14
--- /dev/null
+++ b/console/skel/symfony/http-foundation/Session/Attribute/AttributeBag.php
@@ -0,0 +1,148 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Attribute;
+
+/**
+ * This class relates to session attribute storage.
+ */
+class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable
+{
+ private $name = 'attributes';
+ private $storageKey;
+
+ protected $attributes = array();
+
+ /**
+ * @param string $storageKey The key used to store attributes in the session
+ */
+ public function __construct($storageKey = '_sf2_attributes')
+ {
+ $this->storageKey = $storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialize(array &$attributes)
+ {
+ $this->attributes = &$attributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStorageKey()
+ {
+ return $this->storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($name)
+ {
+ return array_key_exists($name, $this->attributes);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($name, $default = null)
+ {
+ return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($name, $value)
+ {
+ $this->attributes[$name] = $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function replace(array $attributes)
+ {
+ $this->attributes = array();
+ foreach ($attributes as $key => $value) {
+ $this->set($key, $value);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function remove($name)
+ {
+ $retval = null;
+ if (array_key_exists($name, $this->attributes)) {
+ $retval = $this->attributes[$name];
+ unset($this->attributes[$name]);
+ }
+
+ return $retval;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ $return = $this->attributes;
+ $this->attributes = array();
+
+ return $return;
+ }
+
+ /**
+ * Returns an iterator for attributes.
+ *
+ * @return \ArrayIterator An \ArrayIterator instance
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->attributes);
+ }
+
+ /**
+ * Returns the number of attributes.
+ *
+ * @return int The number of attributes
+ */
+ public function count()
+ {
+ return \count($this->attributes);
+ }
+}
diff --git a/console/skel/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php b/console/skel/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php
new file mode 100644
index 0000000..0d8d179
--- /dev/null
+++ b/console/skel/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Attribute;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * Attributes store.
+ *
+ * @author Drak
+ */
+interface AttributeBagInterface extends SessionBagInterface
+{
+ /**
+ * Checks if an attribute is defined.
+ *
+ * @param string $name The attribute name
+ *
+ * @return bool true if the attribute is defined, false otherwise
+ */
+ public function has($name);
+
+ /**
+ * Returns an attribute.
+ *
+ * @param string $name The attribute name
+ * @param mixed $default The default value if not found
+ *
+ * @return mixed
+ */
+ public function get($name, $default = null);
+
+ /**
+ * Sets an attribute.
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function set($name, $value);
+
+ /**
+ * Returns attributes.
+ *
+ * @return array Attributes
+ */
+ public function all();
+
+ /**
+ * Sets attributes.
+ *
+ * @param array $attributes Attributes
+ */
+ public function replace(array $attributes);
+
+ /**
+ * Removes an attribute.
+ *
+ * @param string $name
+ *
+ * @return mixed The removed value or null when it does not exist
+ */
+ public function remove($name);
+}
diff --git a/console/skel/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php b/console/skel/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php
new file mode 100644
index 0000000..2f1e01a
--- /dev/null
+++ b/console/skel/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php
@@ -0,0 +1,159 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Attribute;
+
+/**
+ * This class provides structured storage of session attributes using
+ * a name spacing character in the key.
+ *
+ * @author Drak
+ */
+class NamespacedAttributeBag extends AttributeBag
+{
+ private $namespaceCharacter;
+
+ /**
+ * @param string $storageKey Session storage key
+ * @param string $namespaceCharacter Namespace character to use in keys
+ */
+ public function __construct($storageKey = '_sf2_attributes', $namespaceCharacter = '/')
+ {
+ $this->namespaceCharacter = $namespaceCharacter;
+ parent::__construct($storageKey);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($name)
+ {
+ // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is
+ $attributes = $this->resolveAttributePath($name);
+ $name = $this->resolveKey($name);
+
+ if (null === $attributes) {
+ return false;
+ }
+
+ return array_key_exists($name, $attributes);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($name, $default = null)
+ {
+ // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is
+ $attributes = $this->resolveAttributePath($name);
+ $name = $this->resolveKey($name);
+
+ if (null === $attributes) {
+ return $default;
+ }
+
+ return array_key_exists($name, $attributes) ? $attributes[$name] : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($name, $value)
+ {
+ $attributes = &$this->resolveAttributePath($name, true);
+ $name = $this->resolveKey($name);
+ $attributes[$name] = $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function remove($name)
+ {
+ $retval = null;
+ $attributes = &$this->resolveAttributePath($name);
+ $name = $this->resolveKey($name);
+ if (null !== $attributes && array_key_exists($name, $attributes)) {
+ $retval = $attributes[$name];
+ unset($attributes[$name]);
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Resolves a path in attributes property and returns it as a reference.
+ *
+ * This method allows structured namespacing of session attributes.
+ *
+ * @param string $name Key name
+ * @param bool $writeContext Write context, default false
+ *
+ * @return array
+ */
+ protected function &resolveAttributePath($name, $writeContext = false)
+ {
+ $array = &$this->attributes;
+ $name = (0 === strpos($name, $this->namespaceCharacter)) ? substr($name, 1) : $name;
+
+ // Check if there is anything to do, else return
+ if (!$name) {
+ return $array;
+ }
+
+ $parts = explode($this->namespaceCharacter, $name);
+ if (\count($parts) < 2) {
+ if (!$writeContext) {
+ return $array;
+ }
+
+ $array[$parts[0]] = array();
+
+ return $array;
+ }
+
+ unset($parts[\count($parts) - 1]);
+
+ foreach ($parts as $part) {
+ if (null !== $array && !array_key_exists($part, $array)) {
+ if (!$writeContext) {
+ $null = null;
+
+ return $null;
+ }
+
+ $array[$part] = array();
+ }
+
+ $array = &$array[$part];
+ }
+
+ return $array;
+ }
+
+ /**
+ * Resolves the key from the name.
+ *
+ * This is the last part in a dot separated string.
+ *
+ * @param string $name
+ *
+ * @return string
+ */
+ protected function resolveKey($name)
+ {
+ if (false !== $pos = strrpos($name, $this->namespaceCharacter)) {
+ $name = substr($name, $pos + 1);
+ }
+
+ return $name;
+ }
+}
diff --git a/console/skel/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php b/console/skel/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php
new file mode 100644
index 0000000..0ed6600
--- /dev/null
+++ b/console/skel/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php
@@ -0,0 +1,161 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Flash;
+
+/**
+ * AutoExpireFlashBag flash message container.
+ *
+ * @author Drak
+ */
+class AutoExpireFlashBag implements FlashBagInterface
+{
+ private $name = 'flashes';
+ private $flashes = array('display' => array(), 'new' => array());
+ private $storageKey;
+
+ /**
+ * @param string $storageKey The key used to store flashes in the session
+ */
+ public function __construct($storageKey = '_sf2_flashes')
+ {
+ $this->storageKey = $storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialize(array &$flashes)
+ {
+ $this->flashes = &$flashes;
+
+ // The logic: messages from the last request will be stored in new, so we move them to previous
+ // This request we will show what is in 'display'. What is placed into 'new' this time round will
+ // be moved to display next time round.
+ $this->flashes['display'] = array_key_exists('new', $this->flashes) ? $this->flashes['new'] : array();
+ $this->flashes['new'] = array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function add($type, $message)
+ {
+ $this->flashes['new'][$type][] = $message;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function peek($type, array $default = array())
+ {
+ return $this->has($type) ? $this->flashes['display'][$type] : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function peekAll()
+ {
+ return array_key_exists('display', $this->flashes) ? (array) $this->flashes['display'] : array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($type, array $default = array())
+ {
+ $return = $default;
+
+ if (!$this->has($type)) {
+ return $return;
+ }
+
+ if (isset($this->flashes['display'][$type])) {
+ $return = $this->flashes['display'][$type];
+ unset($this->flashes['display'][$type]);
+ }
+
+ return $return;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all()
+ {
+ $return = $this->flashes['display'];
+ $this->flashes['display'] = array();
+
+ return $return;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setAll(array $messages)
+ {
+ $this->flashes['new'] = $messages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($type, $messages)
+ {
+ $this->flashes['new'][$type] = (array) $messages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($type)
+ {
+ return array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function keys()
+ {
+ return array_keys($this->flashes['display']);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStorageKey()
+ {
+ return $this->storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ return $this->all();
+ }
+}
diff --git a/console/skel/symfony/http-foundation/Session/Flash/FlashBag.php b/console/skel/symfony/http-foundation/Session/Flash/FlashBag.php
new file mode 100644
index 0000000..bc1f8f6
--- /dev/null
+++ b/console/skel/symfony/http-foundation/Session/Flash/FlashBag.php
@@ -0,0 +1,168 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Flash;
+
+/**
+ * FlashBag flash message container.
+ *
+ * \IteratorAggregate implementation is deprecated and will be removed in 3.0.
+ *
+ * @author Drak
+ */
+class FlashBag implements FlashBagInterface, \IteratorAggregate
+{
+ private $name = 'flashes';
+ private $flashes = array();
+ private $storageKey;
+
+ /**
+ * @param string $storageKey The key used to store flashes in the session
+ */
+ public function __construct($storageKey = '_sf2_flashes')
+ {
+ $this->storageKey = $storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialize(array &$flashes)
+ {
+ $this->flashes = &$flashes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function add($type, $message)
+ {
+ $this->flashes[$type][] = $message;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function peek($type, array $default = array())
+ {
+ return $this->has($type) ? $this->flashes[$type] : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function peekAll()
+ {
+ return $this->flashes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($type, array $default = array())
+ {
+ if (!$this->has($type)) {
+ return $default;
+ }
+
+ $return = $this->flashes[$type];
+
+ unset($this->flashes[$type]);
+
+ return $return;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all()
+ {
+ $return = $this->peekAll();
+ $this->flashes = array();
+
+ return $return;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($type, $messages)
+ {
+ $this->flashes[$type] = (array) $messages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setAll(array $messages)
+ {
+ $this->flashes = $messages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($type)
+ {
+ return array_key_exists($type, $this->flashes) && $this->flashes[$type];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function keys()
+ {
+ return array_keys($this->flashes);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStorageKey()
+ {
+ return $this->storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ return $this->all();
+ }
+
+ /**
+ * Returns an iterator for flashes.
+ *
+ * @deprecated since version 2.4, to be removed in 3.0.
+ *
+ * @return \ArrayIterator An \ArrayIterator instance
+ */
+ public function getIterator()
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0.', E_USER_DEPRECATED);
+
+ return new \ArrayIterator($this->all());
+ }
+}
diff --git a/console/skel/symfony/http-foundation/Session/Flash/FlashBagInterface.php b/console/skel/symfony/http-foundation/Session/Flash/FlashBagInterface.php
new file mode 100644
index 0000000..f53c9da
--- /dev/null
+++ b/console/skel/symfony/http-foundation/Session/Flash/FlashBagInterface.php
@@ -0,0 +1,93 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Flash;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * FlashBagInterface.
+ *
+ * @author Drak