diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ + diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..b420d719 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-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/README.md b/README.md index 8fb16649..ce8a4264 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,13 @@ -# A tarefa -Sua tarefa consiste em desenvolver uma API RESTful para manipular um determinado recurso. Deverá ser utilizado o framework Silex. - -# Requisitos -A escolha do recurso deverá ser feita pelo desenvolvedor, atendendo apenas os requisitos mínimos abaixo: - -* Deverá conter um ID -* Deverá conter pelo menos quatro propriedades (exemplos: nome, email, etc.) -* Deverá conter campos que armazenem as datas de criação e alteração do recurso - -A API deverá atender às seguintes exigências: - -* Listagem de todos os recursos -* Busca de um recurso pelo ID -* Criação de um novo recurso -* Alteração de um recurso existente -* Exclusão de um recurso -* Aceitar e retornar apenas JSON -* Deverá possuir algum método de autenticação para utilização de seus endpoints - -# Ferramentas -* PHP -* Banco de dados MySQL -* Framework Silex - -# Fluxo de desenvolvimento -1. Faça um fork deste repositório -2. Crie uma nova branch e nomeie-a com seu usuário do Github -3. Quando o desenvolvimento estiver concluído, faça um pull request - -# Padrões de nomenclatura -1. Código fonte, nome do banco de dados, tabelas e campos devem estar em inglês - -**Inclua no seu commit todos os arquivos necessários para que possamos testar o código.** +# Sobre o projeto +Eu escolhi algumas publicações do site da GFT Brasil como o recurso da minha API. De acordo com os requisitos, é possível inserir/alterar/remover as publicações. + +# Deploy +* Configurar as credenciais do banco de dados em src/app.php +* Importar o arquivo database.sql que está na raíz + +# Instruções de uso +* Nome do recurso com as publicações: releases +* Obter token com PUT /tokens e JSON body {"username": "gft", "password": "1234"} +* Repassar o token em todos os endpoints como parâmetro GET _token. Exemplo: /releases?_token=e3b50681ffdf0fbe7dfb25acbb809ed0 (o token não possui expiração) +* Os usuários podem ser incluídos somente diretamente no banco de dados +* No endpoint GET /releases, é possível enviar um parâmetro "query" para efetuar uma busca fulltext \ No newline at end of file diff --git a/bin/console b/bin/console new file mode 100755 index 00000000..8ff5b7c3 --- /dev/null +++ b/bin/console @@ -0,0 +1,16 @@ +#!/usr/bin/env php +getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev'); + +$app = require __DIR__.'/../src/app.php'; +require __DIR__.'/../config/'.$env.'.php'; +$console = require __DIR__.'/../src/console.php'; +$console->run(); diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..28dd1340 --- /dev/null +++ b/composer.json @@ -0,0 +1,41 @@ +{ + "name": "fabpot/silex-skeleton", + "description": "A pre-configured skeleton for the Silex microframework", + "license": "MIT", + "type": "project", + "require": { + "php": ">=5.5.9", + "silex/silex": "~2.0", + "silex/web-profiler": "~2.0", + "symfony/asset": "~2.8|^3.0", + "symfony/browser-kit": "~2.8|^3.0", + "symfony/class-loader": "~2.8|^3.0", + "symfony/config": "~2.8|^3.0", + "symfony/console": "~2.8|^3.0", + "symfony/css-selector": "~2.8|^3.0", + "symfony/debug": "~2.8|^3.0", + "symfony/finder": "~2.8|^3.0", + "symfony/form": "~2.8|^3.0", + "symfony/monolog-bridge": "~2.8|^3.0", + "symfony/process": "~2.8|^3.0", + "symfony/security": "~2.8|^3.0", + "symfony/translation": "~2.8|^3.0", + "symfony/twig-bridge": "~2.8|^3.0", + "symfony/validator": "~2.8|^3.0", + "doctrine/dbal": "~2.2" + }, + "autoload": { + "psr-0": { "": "src/" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "scripts": { + "run": [ + "echo 'Started web server on http://localhost:8888'", + "php -S localhost:8888 -t web" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..ea1d34d9 --- /dev/null +++ b/composer.lock @@ -0,0 +1,2979 @@ +{ + "_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": "f54bb5099c1027d7c922ab9345169200", + "packages": [ + { + "name": "doctrine/annotations", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "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": "2017-02-24T16:22:25+00:00" + }, + { + "name": "doctrine/cache", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/b6f544a20f4807e81f7044d31e679ccbb1866dc3", + "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3", + "shasum": "" + }, + "require": { + "php": "~5.5|~7.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "phpunit/phpunit": "~4.8|~5.0", + "predis/predis": "~1.0", + "satooshi/php-coveralls": "~0.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ], + "time": "2016-10-29T11:16:17+00:00" + }, + { + "name": "doctrine/collections", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "~0.1@dev", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "array", + "collections", + "iterator" + ], + "time": "2017-01-03T10:49:41+00:00" + }, + { + "name": "doctrine/common", + "version": "v2.7.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "930297026c8009a567ac051fd545bf6124150347" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/930297026c8009a567ac051fd545bf6124150347", + "reference": "930297026c8009a567ac051fd545bf6124150347", + "shasum": "" + }, + "require": { + "doctrine/annotations": "1.*", + "doctrine/cache": "1.*", + "doctrine/collections": "1.*", + "doctrine/inflector": "1.*", + "doctrine/lexer": "1.*", + "php": "~5.6|~7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common Library for Doctrine projects", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "collections", + "eventmanager", + "persistence", + "spl" + ], + "time": "2017-01-13T14:02:13+00:00" + }, + { + "name": "doctrine/dbal", + "version": "v2.5.12", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "7b9e911f9d8b30d43b96853dab26898c710d8f44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/7b9e911f9d8b30d43b96853dab26898c710d8f44", + "reference": "7b9e911f9d8b30d43b96853dab26898c710d8f44", + "shasum": "" + }, + "require": { + "doctrine/common": ">=2.4,<2.8-dev", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*", + "symfony/console": "2.*||^3.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.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\DBAL\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Database Abstraction Layer", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "persistence", + "queryobject" + ], + "time": "2017-02-08T12:53:47+00:00" + }, + { + "name": "doctrine/inflector", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Inflector\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "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": "2015-11-06T14:35:42+00:00" + }, + { + "name": "doctrine/lexer", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "time": "2014-09-09T13:34:57+00:00" + }, + { + "name": "monolog/monolog", + "version": "1.22.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1e044bc4b34e91743943479f1be7a1d5eb93add0", + "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "~5.3" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "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", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.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": "2017-03-13T07:08:03+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.10", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d", + "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2017-03-13T16:27:32+00:00" + }, + { + "name": "pimple/pimple", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ], + "time": "2015-09-11T15:10:35+00:00" + }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.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": "2016-10-10T12:19:37+00:00" + }, + { + "name": "silex/silex", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Silex.git", + "reference": "d5a9d9af14a1424ddecc3da481769cf64e7d3b34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Silex/zipball/d5a9d9af14a1424ddecc3da481769cf64e7d3b34", + "reference": "d5a9d9af14a1424ddecc3da481769cf64e7d3b34", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0", + "symfony/event-dispatcher": "~2.8|^3.0", + "symfony/http-foundation": "~2.8|^3.0", + "symfony/http-kernel": "~2.8|^3.0", + "symfony/routing": "~2.8|^3.0" + }, + "replace": { + "silex/api": "self.version", + "silex/providers": "self.version" + }, + "require-dev": { + "doctrine/dbal": "~2.2", + "monolog/monolog": "^1.4.1", + "swiftmailer/swiftmailer": "~5", + "symfony/asset": "~2.8|^3.0", + "symfony/browser-kit": "~2.8|^3.0", + "symfony/config": "~2.8|^3.0", + "symfony/css-selector": "~2.8|^3.0", + "symfony/debug": "~2.8|^3.0", + "symfony/doctrine-bridge": "~2.8|^3.0", + "symfony/dom-crawler": "~2.8|^3.0", + "symfony/expression-language": "~2.8|^3.0", + "symfony/finder": "~2.8|^3.0", + "symfony/form": "~2.8|^3.0", + "symfony/intl": "~2.8|^3.0", + "symfony/monolog-bridge": "~2.8|^3.0", + "symfony/options-resolver": "~2.8|^3.0", + "symfony/phpunit-bridge": "^3.2", + "symfony/process": "~2.8|^3.0", + "symfony/security": "~2.8|^3.0", + "symfony/serializer": "~2.8|^3.0", + "symfony/translation": "~2.8|^3.0", + "symfony/twig-bridge": "~2.8|^3.0", + "symfony/validator": "~2.8|^3.0", + "symfony/var-dumper": "~2.8|^3.0", + "symfony/web-link": "^3.3", + "twig/twig": "~1.28|~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Silex\\": "src/Silex" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "The PHP micro-framework based on the Symfony Components", + "homepage": "http://silex.sensiolabs.org", + "keywords": [ + "microframework" + ], + "time": "2017-05-03T15:21:42+00:00" + }, + { + "name": "silex/web-profiler", + "version": "v2.0.9", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Silex-WebProfiler.git", + "reference": "cd76e55490d556a81378b8230e28f55536811418" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Silex-WebProfiler/zipball/cd76e55490d556a81378b8230e28f55536811418", + "reference": "cd76e55490d556a81378b8230e28f55536811418", + "shasum": "" + }, + "require": { + "silex/silex": "^2.0", + "symfony/stopwatch": "^2.8|^3.0", + "symfony/twig-bridge": "^2.8|^3.0", + "symfony/web-profiler-bundle": "^2.8|^3.0" + }, + "conflict": { + "symfony/web-profiler-bundle": "3.1.0" + }, + "require-dev": { + "symfony/browser-kit": "^2.8|^3.0", + "symfony/css-selector": "^2.8|^3.0", + "symfony/debug-bundle": "^2.8|^3.0", + "symfony/phpunit-bridge": "~3.2", + "symfony/security": "^2.8|^3.0", + "symfony/security-bundle": "^2.8|^3.0", + "symfony/translation": "^2.8|^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Silex\\Provider\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A WebProfiler for Silex", + "homepage": "http://silex.sensiolabs.org/", + "time": "2017-04-23T23:11:39+00:00" + }, + { + "name": "symfony/asset", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset.git", + "reference": "67ee69a32a4abd97bb14f938e51e714fbb38555b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset/zipball/67ee69a32a4abd97bb14f938e51e714fbb38555b", + "reference": "67ee69a32a4abd97bb14f938e51e714fbb38555b", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/http-foundation": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0" + }, + "suggest": { + "symfony/http-foundation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-05-01T15:01:29+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "c2c8ceb1aa9dab9eae54e9150e6a588ce3e53be1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/c2c8ceb1aa9dab9eae54e9150e6a588ce3e53be1", + "reference": "c2c8ceb1aa9dab9eae54e9150e6a588ce3e53be1", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/dom-crawler": "~2.8|~3.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-04-12T14:14:56+00:00" + }, + { + "name": "symfony/class-loader", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/class-loader.git", + "reference": "386a294d621576302e7cc36965d6ed53b8c73c4f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/386a294d621576302e7cc36965d6ed53b8c73c4f", + "reference": "386a294d621576302e7cc36965d6ed53b8c73c4f", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/finder": "~2.8|~3.0", + "symfony/polyfill-apcu": "~1.1" + }, + "suggest": { + "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\ClassLoader\\": "" + }, + "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 ClassLoader Component", + "homepage": "https://symfony.com", + "time": "2017-06-02T09:51:43+00:00" + }, + { + "name": "symfony/config", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "35716d4904e0506a7a5a9bcf23f854aeb5719bca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/35716d4904e0506a7a5a9bcf23f854aeb5719bca", + "reference": "35716d4904e0506a7a5a9bcf23f854aeb5719bca", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/filesystem": "~2.8|~3.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "symfony/dependency-injection": "~3.3", + "symfony/yaml": "~3.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-06-02T18:07:20+00:00" + }, + { + "name": "symfony/console", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "70d2a29b2911cbdc91a7e268046c395278238b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/70d2a29b2911cbdc91a7e268046c395278238b2e", + "reference": "70d2a29b2911cbdc91a7e268046c395278238b2e", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/debug": "~2.8|~3.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.3", + "symfony/dependency-injection": "~3.3", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/filesystem": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/filesystem": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-06-02T19:24:58+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "4d882dced7b995d5274293039370148e291808f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/4d882dced7b995d5274293039370148e291808f2", + "reference": "4d882dced7b995d5274293039370148e291808f2", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2017-05-01T15:01:29+00:00" + }, + { + "name": "symfony/debug", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/e9c50482841ef696e8fa1470d950a79c8921f45d", + "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "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 Debug Component", + "homepage": "https://symfony.com", + "time": "2017-06-01T21:01:25+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "fc2c588ce376e9fe04a7b8c79e3ec62fe32d95b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/fc2c588ce376e9fe04a7b8c79e3ec62fe32d95b1", + "reference": "fc2c588ce376e9fe04a7b8c79e3ec62fe32d95b1", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-05-25T23:10:31+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "4054a102470665451108f9b59305c79176ef98f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4054a102470665451108f9b59305c79176ef98f0", + "reference": "4054a102470665451108f9b59305c79176ef98f0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-06-04T18:15:29+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "c709670bf64721202ddbe4162846f250735842c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/c709670bf64721202ddbe4162846f250735842c0", + "reference": "c709670bf64721202ddbe4162846f250735842c0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-05-28T14:08:56+00:00" + }, + { + "name": "symfony/finder", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4", + "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-06-01T21:01:25+00:00" + }, + { + "name": "symfony/form", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/form.git", + "reference": "585d656096e87f0ee1443dbeeb9bc471bcc858cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/form/zipball/585d656096e87f0ee1443dbeeb9bc471bcc858cd", + "reference": "585d656096e87f0ee1443dbeeb9bc471bcc858cd", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/intl": "^2.8.18|^3.2.5", + "symfony/options-resolver": "~2.8|~3.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/property-access": "~2.8|~3.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/dependency-injection": "<3.3", + "symfony/doctrine-bridge": "<2.7", + "symfony/framework-bundle": "<2.7", + "symfony/twig-bridge": "<2.7", + "symfony/var-dumper": "<3.3" + }, + "require-dev": { + "doctrine/collections": "~1.0", + "symfony/config": "~2.7|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0", + "symfony/security-csrf": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/validator": "^2.8.18|^3.2.5", + "symfony/var-dumper": "~3.3" + }, + "suggest": { + "symfony/framework-bundle": "For templating with PHP.", + "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": "3.3-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": "2017-06-06T03:13:52+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/80eb5a1f968448b77da9e8b2c0827f6e8d767846", + "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-06-05T13:06:51+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "be8280f7fa8e95b86514f1e1be997668a53b2888" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/be8280f7fa8e95b86514f1e1be997668a53b2888", + "reference": "be8280f7fa8e95b86514f1e1be997668a53b2888", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0", + "symfony/debug": "~2.8|~3.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "~3.3" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/var-dumper": "<3.3", + "twig/twig": "<1.34|<2.4,>=2" + }, + "require-dev": { + "psr/cache": "~1.0", + "symfony/browser-kit": "~2.8|~3.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/css-selector": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/dom-crawler": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0", + "symfony/routing": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~3.3" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-06-06T03:59:58+00:00" + }, + { + "name": "symfony/inflector", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/inflector.git", + "reference": "aed5a0874a3bcfd8d0393a2d91b4cf828f29c7fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/inflector/zipball/aed5a0874a3bcfd8d0393a2d91b4cf828f29c7fb", + "reference": "aed5a0874a3bcfd8d0393a2d91b4cf828f29c7fb", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-04-12T14:14:56+00:00" + }, + { + "name": "symfony/intl", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/intl.git", + "reference": "a310b29a794578f544305f4f48e3f6c7981b606c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/intl/zipball/a310b29a794578f544305f4f48e3f6c7981b606c", + "reference": "a310b29a794578f544305f4f48e3f6c7981b606c", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-intl-icu": "~1.0" + }, + "require-dev": { + "symfony/filesystem": "~2.8|~3.0" + }, + "suggest": { + "ext-intl": "to use the component with locales other than \"en\"" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-06-01T21:01:25+00:00" + }, + { + "name": "symfony/monolog-bridge", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bridge.git", + "reference": "c659d3a02eb39c8e524bf5933620f9f83d9a4167" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/c659d3a02eb39c8e524bf5933620f9f83d9a4167", + "reference": "c659d3a02eb39c8e524bf5933620f9f83d9a4167", + "shasum": "" + }, + "require": { + "monolog/monolog": "~1.19", + "php": ">=5.5.9", + "symfony/http-kernel": "~2.8|~3.0" + }, + "conflict": { + "symfony/http-foundation": "<3.3" + }, + "require-dev": { + "symfony/console": "~2.8|~3.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/var-dumper": "~3.3" + }, + "suggest": { + "symfony/console": "For the possibility to show log messages in console commands depending on verbosity settings. You need version ~2.3 of the console for it.", + "symfony/event-dispatcher": "Needed when using log messages in console commands.", + "symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel." + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-05-20T07:30:49+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/ff48982d295bcac1fd861f934f041ebc73ae40f0", + "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-04-12T14:14:56+00:00" + }, + { + "name": "symfony/polyfill-intl-icu", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-icu.git", + "reference": "2d6e2b20d457603eefb6e614286c22efca30fdb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/2d6e2b20d457603eefb6e614286c22efca30fdb4", + "reference": "2d6e2b20d457603eefb6e614286c22efca30fdb4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/intl": "~2.3|~3.0" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-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": "2016-11-14T01:06:16+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-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": "2016-11-14T01:06:16+00:00" + }, + { + "name": "symfony/polyfill-php56", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/1dd42b9b89556f18092f3d1ada22cb05ac85383c", + "reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "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 backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2016-11-14T01:06:16+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "13ce343935f0f91ca89605a2f6ca6f5c2f3faac2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/13ce343935f0f91ca89605a2f6ca6f5c2f3faac2", + "reference": "13ce343935f0f91ca89605a2f6ca6f5c2f3faac2", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "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.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2016-11-14T01:06:16+00:00" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "746bce0fca664ac0a575e465f65c6643faddf7fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/746bce0fca664ac0a575e465f65c6643faddf7fb", + "reference": "746bce0fca664ac0a575e465f65c6643faddf7fb", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "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 utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ], + "time": "2016-11-14T01:06:16+00:00" + }, + { + "name": "symfony/process", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "8e30690c67aafb6c7992d6d8eb0d707807dd3eaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/8e30690c67aafb6c7992d6d8eb0d707807dd3eaf", + "reference": "8e30690c67aafb6c7992d6d8eb0d707807dd3eaf", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-05-22T12:32:03+00:00" + }, + { + "name": "symfony/property-access", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-access.git", + "reference": "70bbfe28f77070d41957495bc6fed34f139fe6f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-access/zipball/70bbfe28f77070d41957495bc6fed34f139fe6f7", + "reference": "70bbfe28f77070d41957495bc6fed34f139fe6f7", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/inflector": "~3.1", + "symfony/polyfill-php70": "~1.0" + }, + "require-dev": { + "symfony/cache": "~3.1" + }, + "suggest": { + "psr/cache-implementation": "To cache access methods." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-06-02T09:51:43+00:00" + }, + { + "name": "symfony/routing", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "39804eeafea5cca851946e1eed122eb94459fdb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/39804eeafea5cca851946e1eed122eb94459fdb4", + "reference": "39804eeafea5cca851946e1eed122eb94459fdb4", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.3" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/yaml": "~3.3" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/dependency-injection": "For loading routes from a service", + "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": "3.3-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": "2017-06-02T09:51:43+00:00" + }, + { + "name": "symfony/security", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/security.git", + "reference": "b92647f30b03f9d749c9343b39acef7e28bf3f9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security/zipball/b92647f30b03f9d749c9343b39acef7e28bf3f9a", + "reference": "b92647f30b03f9d749c9343b39acef7e28bf3f9a", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/http-kernel": "~3.3", + "symfony/polyfill-php56": "~1.0", + "symfony/polyfill-php70": "~1.0", + "symfony/polyfill-util": "~1.0", + "symfony/property-access": "~2.8|~3.0" + }, + "replace": { + "symfony/security-core": "self.version", + "symfony/security-csrf": "self.version", + "symfony/security-guard": "self.version", + "symfony/security-http": "self.version" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/ldap": "~3.1", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/routing": "~2.8|~3.0", + "symfony/validator": "^2.8.18|^3.2.5" + }, + "suggest": { + "symfony/expression-language": "For using the expression voter", + "symfony/form": "", + "symfony/ldap": "For using the LDAP user and authentication providers", + "symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs", + "symfony/validator": "For using the user password constraint" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\": "" + }, + "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", + "homepage": "https://symfony.com", + "time": "2017-06-03T08:00:53+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "602a15299dc01556013b07167d4f5d3a60e90d15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/602a15299dc01556013b07167d4f5d3a60e90d15", + "reference": "602a15299dc01556013b07167d4f5d3a60e90d15", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-04-12T14:14:56+00:00" + }, + { + "name": "symfony/translation", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/dc3b2a0c6cfff60327ba1c043a82092735397543", + "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/yaml": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/intl": "^2.8.18|^3.2.5", + "symfony/yaml": "~3.3" + }, + "suggest": { + "psr/log": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-05-22T07:42:36+00:00" + }, + { + "name": "symfony/twig-bridge", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "7f1bc6676396441dc938dc4dacb49ae91f56b930" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/7f1bc6676396441dc938dc4dacb49ae91f56b930", + "reference": "7f1bc6676396441dc938dc4dacb49ae91f56b930", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "twig/twig": "~1.34|~2.4" + }, + "require-dev": { + "fig/link-util": "^1.0", + "symfony/asset": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/form": "^3.2.7", + "symfony/http-kernel": "~3.2", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/routing": "~2.8|~3.0", + "symfony/security": "~2.8|~3.0", + "symfony/security-acl": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~2.8.10|~3.1.4|~3.2", + "symfony/web-link": "~3.3", + "symfony/yaml": "~2.8|~3.0" + }, + "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": "For using the SecurityExtension", + "symfony/stopwatch": "For using the StopwatchExtension", + "symfony/templating": "For using the TwigEngine", + "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": "3.3-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": "2017-06-06T03:13:52+00:00" + }, + { + "name": "symfony/validator", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/validator.git", + "reference": "07888aa5384e3a6cbdb29d158f5aaf99f67e1b88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/validator/zipball/07888aa5384e3a6cbdb29d158f5aaf99f67e1b88", + "reference": "07888aa5384e3a6cbdb29d158f5aaf99f67e1b88", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation": "~2.8|~3.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.3" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/cache": "~1.0", + "egulias/email-validator": "^1.2.8|~2.0", + "symfony/cache": "~3.1", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/intl": "^2.8.18|^3.2.5", + "symfony/yaml": "~3.3" + }, + "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.", + "egulias/email-validator": "Strict (RFC compliant) email validation", + "psr/cache-implementation": "For using the metadata cache.", + "symfony/config": "", + "symfony/expression-language": "For using the Expression validator", + "symfony/http-foundation": "", + "symfony/intl": "", + "symfony/property-access": "For using the Expression validator", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-06-02T14:38:05+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "347c4247a3e40018810b476fcd5dec36d46d08dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/347c4247a3e40018810b476fcd5dec36d46d08dc", + "reference": "347c4247a3e40018810b476fcd5dec36d46d08dc", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, + "require-dev": { + "ext-iconv": "*", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-symfony_debug": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-06-02T09:10:29+00:00" + }, + { + "name": "symfony/web-profiler-bundle", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/web-profiler-bundle.git", + "reference": "77a0722c855f1b2f44bdbd043935ccaf50fb8bc5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/77a0722c855f1b2f44bdbd043935ccaf50fb8bc5", + "reference": "77a0722c855f1b2f44bdbd043935ccaf50fb8bc5", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/http-kernel": "~3.2", + "symfony/polyfill-php70": "~1.0", + "symfony/routing": "~2.8|~3.0", + "symfony/twig-bridge": "~2.8|~3.0", + "symfony/var-dumper": "~3.3", + "twig/twig": "~1.34|~2.4" + }, + "conflict": { + "symfony/dependency-injection": "<3.3", + "symfony/event-dispatcher": "<3.2", + "symfony/var-dumper": "<3.3" + }, + "require-dev": { + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/stopwatch": "~2.8|~3.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "3.3-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": "2017-06-03T11:15:34+00:00" + }, + { + "name": "twig/twig", + "version": "v2.4.3", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "eab7c3288ae6603d7d6f92b531626af2b162d1f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/eab7c3288ae6603d7d6f92b531626af2b162d1f2", + "reference": "eab7c3288ae6603d7d6f92b531626af2b162d1f2", + "shasum": "" + }, + "require": { + "php": "^7.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/debug": "~2.7", + "symfony/phpunit-bridge": "~3.3@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_": "lib/" + }, + "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": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + }, + { + "name": "Twig Team", + "homepage": "http://twig.sensiolabs.org/contributors", + "role": "Contributors" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "http://twig.sensiolabs.org", + "keywords": [ + "templating" + ], + "time": "2017-06-07T18:47:58+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.5.9" + }, + "platform-dev": [] +} diff --git a/config/prod.php b/config/prod.php new file mode 100644 index 00000000..769ee7ab --- /dev/null +++ b/config/prod.php @@ -0,0 +1,3 @@ + __DIR__.'/../var/cache/twig'); \ No newline at end of file diff --git a/database.sql b/database.sql new file mode 100644 index 00000000..7fcf2319 --- /dev/null +++ b/database.sql @@ -0,0 +1,36 @@ +CREATE TABLE releases ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + description TEXT NULL, + link VARCHAR(255) NOT NULL, + tags JSON NULL, + title VARCHAR(255) NULL, + updated_at TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE INDEX link (link ASC), + INDEX title (title ASC), + FULLTEXT INDEX search (title ASC, description ASC), + INDEX created_at (created_at ASC)); + +CREATE TABLE users ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT, + password CHAR(32) NOT NULL, + token CHAR(32) NULL, + username VARCHAR(255) NOT NULL, + PRIMARY KEY (id), + UNIQUE INDEX username (username ASC), + INDEX login (username ASC, password ASC), + INDEX token (token ASC)); + +INSERT INTO users (username, password) VALUES("gft", MD5('1234')); + +INSERT INTO releases (description, link, tags, title) VALUES ('The economic and regulatory environment of recent years has placed a powerful downward pressure on any real investment within banks and other financial services firms, whilst at the same time diverting a significant portion of remaining resources to coping with rapidly evolving regulatory requirements.','http://www.gft.com/br/pt/index/discovery/publicacoes/the-complexity-challenge/','["Mercado de capitais","Regulatory Compliance","Operational efficiency"]','The complexity challenge'); +INSERT INTO releases (description, link, tags, title) VALUES ('The financial services regulatory environment is in a state of constant flux. Many legislative bodies around the globe publish regulations that are growing in both volume and complexity.','http://www.gft.com/br/pt/index/discovery/publicacoes/delivering-regulatory-change-management-time-for-a-smart-approach/','["Mercado de capitais","Regulatory Compliance","Regulatory Change Management Service"]','Delivering regulatory change management: Time for a smart approach?'); +INSERT INTO releases (description, link, tags, title) VALUES ('Information technology is increasingly pervading every aspect of our lives, promising fantastic opportunities for improving our health, wealth, knowledge and the way we live together.','http://www.gft.com/br/pt/index/discovery/publicacoes/general-data-protection-regulation-a-route-to-nirvana-for-client-data/','["Mercado de capitais","Regulatory Compliance","Banking","Regulatory Compliance"]','General Data Protection Regulation - a route to nirvana for client data?'); +INSERT INTO releases (description, link, tags, title) VALUES ('There is still uncertainty regarding the extent of capital impact, it falls within tight parameters, as neatly expressed in the recent ISDA FRTB QIS4 Refresh summary slide, which puts capital increase factor in the range 2.4 and 1.5. ','http://www.gft.com/br/pt/index/discovery/publicacoes/frtb-now-that-the-ink-has-dried/','["Mercado de capitais","Regulatory Compliance","Risk Management"]','FRTB: Now that the ink has dried ...'); +INSERT INTO releases (description, link, tags, title) VALUES ('GFT collateral specialists, led by Nick Nicholls, analyse how Triparty facilities could be an integral part of the holistic model.','http://www.gft.com/br/pt/index/discovery/publicacoes/the-triparty-otc-collateral-conundrum/','["Mercado de capitais","Regulatory Compliance","Collateral Optimiser","Risk Management"]','The Triparty OTC collateral conundrum'); +INSERT INTO releases (description, link, tags, title) VALUES ('In this new digital world, complex and multi-faceted interaction with information has created tremendous challenges for design research. Though how does a researcher capture a holistic perspective from which to design?','http://www.gft.com/br/pt/index/discovery/publicacoes/mixed-methods-effectively-gaining-research-insights-in-the-new-digital-world/','["Mercado de capitais"]','Mixed Methods - Effectively gaining research insights in the new digital world'); +INSERT INTO releases (description, link, tags, title) VALUES ('GFT regulation specialists analyse the best way to break down MiFID II and why this is no time to delay. ','http://www.gft.com/br/pt/index/discovery/publicacoes/mifid-ll-how-to-eat-the-elephant/','["Mercado de capitais"]','MiFID II: How to eat the elephant'); +INSERT INTO releases (description, link, tags, title) VALUES ('GFT specialists look into the recent Blockchain phenomenon and provide a blueprint for initiating business change.','http://www.gft.com/br/pt/index/discovery/publicacoes/a-blueprint-for-initiating-business-change-using-blockchain/','["Mercado de capitais"]','A blueprint for initiating business change using Blockchain'); +INSERT INTO releases (description, link, tags, title) VALUES ('Agile Banking provides a new path to compete with both FinTechs and major internet players who enter the world of banking. +','http://www.gft.com/br/pt/index/discovery/publicacoes/agile-banking/','["Banking"]','Agile Banking'); \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 00000000..64126f25 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,23 @@ + + + + + + + + src/ + tests/ + + + + diff --git a/spider.php b/spider.php new file mode 100644 index 00000000..fb7af478 --- /dev/null +++ b/spider.php @@ -0,0 +1,17 @@ +register(new ServiceControllerServiceProvider()); +$app->register(new AssetServiceProvider()); +$app->register(new TwigServiceProvider()); +$app->register(new HttpFragmentServiceProvider()); + +$app['twig'] = $app->extend('twig', function ($twig, $app) { + return $twig; +}); + +$app->register(new Silex\Provider\DoctrineServiceProvider(), [ + 'db.options' => [ + 'driver' => 'pdo_mysql', + 'dbname' => 'gft_api', + 'host' => '127.0.0.1', + 'user' => 'gft', + 'password' => 'hireme' + ] +]); + +return $app; diff --git a/src/console.php b/src/console.php new file mode 100644 index 00000000..465e14f3 --- /dev/null +++ b/src/console.php @@ -0,0 +1,23 @@ +getDefinition()->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', 'dev')); +$console->setDispatcher($app['dispatcher']); +$console + ->register('my-command') + ->setDefinition(array( + // new InputOption('some-option', null, InputOption::VALUE_NONE, 'Some help'), + )) + ->setDescription('My command description') + ->setCode(function (InputInterface $input, OutputInterface $output) use ($app) { + // do something + }) +; + +return $console; diff --git a/src/controllers.php b/src/controllers.php new file mode 100644 index 00000000..6bc22460 --- /dev/null +++ b/src/controllers.php @@ -0,0 +1,167 @@ +before(function (Request $request) use ($app) { + $data = json_decode($request->getContent(), true); + $request->request->replace(is_array($data) ? $data : []); + + if ($request->getPathInfo() != '/tokens') { + // Validate token + if ($request->get('_token') == '') $app->abort(403); + $query = 'SELECT COUNT(*) FROM users WHERE token = ?'; + $found = $app['db']->fetchColumn($query, [$request->get('_token')]); + if (!$found) $app->abort(403); + } +}); + +$app->get('/', function (Application $app) { + return $app->json(['resources' => ['releases']]); +}); + +$app->put('/tokens', function (Application $app, Request $request) { + $query = 'SELECT * FROM users WHERE username = ? AND password = MD5(?)'; + $user = $app['db']->fetchAssoc($query, [ + $request->request->get('username'), + $request->request->get('password') + ]); + + if (!$user) { + return $app->json(['message' => 'User not found.'], 404); + } + + $token = md5(rand()); + $app['db']->update('users', ['token' => $token], ['id' => $user['id']]); + + return $app->json(['token' => $token], 201); +}); + +$app->get('/releases', function (Application $app, Request $request) { + $query = 'SELECT * FROM releases'; + $bind = []; + + // Optional search query + if ($request->get('query')) { + $query .= ' WHERE MATCH (title, description) AGAINST (? IN BOOLEAN MODE)'; + $bind[] = $request->get('query'); + } + + $releases = $app['db']->fetchAll($query, $bind); + + foreach ($releases as $key => $release) { + $releases[$key]['tags'] = json_decode($release['tags'], true); + } + + return $app->json(['releases' => $releases]); +}); + +$app->put('/releases', function (Application $app, Request $request) { + if ($request->headers->get('Content-Type') != 'application/json') { + $app->abort(400); + } + + $release = []; + + if (empty($request->request->get('link'))) { + $app->abort(400, 'Link is required.'); + } + + $release['link'] = $request->request->get('link'); + + if ($request->request->get('description')) { + $release['description'] = $request->request->get('description'); + } + + if ($request->request->get('tags')) { + $release['tags'] = json_encode($request->request->get('tags')); + } + + if ($request->request->get('title')) { + $release['title'] = $request->request->get('title'); + } + + try { + $app['db']->insert('releases', $release); + $id = $app['db']->lastInsertId(); + } catch (\Exception $e) { + throw new \Exception('Error while inserting release.'); + } + + return $app->json(['id' => $id], 201); +}); + +$app->post('/releases/{id}', function (Application $app, Request $request, $id) { + $query = 'SELECT * FROM releases WHERE id = ?'; + $release = $app['db']->fetchAssoc($query, [(int) $id]); + + if (!$release) { + return $app->json(['message' => 'Release not found.'], 404); + } + + if ($request->request->get('description')) { + $release['description'] = $request->request->get('description'); + } + + if ($request->request->get('link')) { + $release['link'] = $request->request->get('link'); + } + + if ($request->request->get('tags')) { + $release['tags'] = json_encode($request->request->get('tags')); + } + + if ($request->request->get('title')) { + $release['title'] = $request->request->get('title'); + } + + try { + $app['db']->update('releases', $release, ['id' => $release['id']]); + } catch (\Exception $e) { + throw new \Exception('Error while updating release.'); + } + + return new Response(null, 200); +}); + +$app->delete('/releases/{id}', function (Application $app, $id) { + $query = 'SELECT * FROM releases WHERE id = ?'; + $release = $app['db']->fetchAssoc($query, [(int) $id]); + + if (!$release) { + return $app->json(['message' => 'Release not found.'], 404); + } + + $app->delete('releases', ['id' => $release['id']]); + + return new Response(null, 200); +}); + +$app->get('/releases/{id}', function (Application $app, $id) { + $query = 'SELECT * FROM releases WHERE id = ?'; + $release = $app['db']->fetchAssoc($query, [(int) $id]); + + if (!$release) { + return $app->json(['message' => 'Release not found.'], 404); + } + + $release['tags'] = json_decode($release['tags'], true); + + return $app->json($release); +}); + +$app->error(function (\Exception $e, Request $request, $code) use ($app) { + if ($e->getMessage() != '') { + return $app->json(['message' => $e->getMessage()], $code); + } else { + return new Response(null, $code); + } +}); diff --git a/tests/controllersTest.php b/tests/controllersTest.php new file mode 100644 index 00000000..c3f10eda --- /dev/null +++ b/tests/controllersTest.php @@ -0,0 +1,26 @@ +createClient(); + $client->followRedirects(true); + $crawler = $client->request('GET', '/'); + + $this->assertTrue($client->getResponse()->isOk()); + $this->assertContains('Welcome', $crawler->filter('body')->text()); + } + + public function createApplication() + { + $app = require __DIR__.'/../src/app.php'; + require __DIR__.'/../config/dev.php'; + require __DIR__.'/../src/controllers.php'; + $app['session.test'] = true; + + return $this->app = $app; + } +} diff --git a/var/cache/.gitignore b/var/cache/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/var/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/var/logs/.gitignore b/var/logs/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/var/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 00000000..27866a14 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + foreach ($this->prefixDirsPsr4[$search] as $dir) { + $length = $this->prefixLengthsPsr4[$first][$search]; + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 00000000..62ecfd8d --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) Nils Adermann, Jordi Boggiano + +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/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 00000000..90d4a754 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,19 @@ + $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php', + 'AssertionError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php', + 'Collator' => $vendorDir . '/symfony/intl/Resources/stubs/Collator.php', + 'DivisionByZeroError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php', + 'Error' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/Error.php', + 'IntlDateFormatter' => $vendorDir . '/symfony/intl/Resources/stubs/IntlDateFormatter.php', + 'Locale' => $vendorDir . '/symfony/intl/Resources/stubs/Locale.php', + 'NumberFormatter' => $vendorDir . '/symfony/intl/Resources/stubs/NumberFormatter.php', + 'ParseError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ParseError.php', + 'TypeError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/TypeError.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 00000000..10d5e88e --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,15 @@ + $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php', + '023d27dca8066ef29e6739335ea73bad' => $vendorDir . '/symfony/polyfill-php70/bootstrap.php', + '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', + '6a47392539ca2329373e0d33e1dba053' => $vendorDir . '/symfony/polyfill-intl-icu/bootstrap.php', + 'bd9634f2d41831496de0d3dfe4c94881' => $vendorDir . '/symfony/polyfill-php56/bootstrap.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 00000000..0adde84d --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,16 @@ + array($vendorDir . '/twig/twig/lib'), + 'Pimple' => array($vendorDir . '/pimple/pimple/src'), + 'Doctrine\\DBAL\\' => array($vendorDir . '/doctrine/dbal/lib'), + 'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib'), + 'Doctrine\\Common\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib'), + 'Doctrine\\Common\\Collections\\' => array($vendorDir . '/doctrine/collections/lib'), + '' => array($baseDir . '/src'), +); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php new file mode 100644 index 00000000..6324c02d --- /dev/null +++ b/vendor/composer/autoload_psr4.php @@ -0,0 +1,49 @@ + array($vendorDir . '/twig/twig/src'), + 'Symfony\\Polyfill\\Util\\' => array($vendorDir . '/symfony/polyfill-util'), + 'Symfony\\Polyfill\\Php70\\' => array($vendorDir . '/symfony/polyfill-php70'), + 'Symfony\\Polyfill\\Php56\\' => array($vendorDir . '/symfony/polyfill-php56'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'), + 'Symfony\\Component\\Validator\\' => array($vendorDir . '/symfony/validator'), + 'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'), + 'Symfony\\Component\\Stopwatch\\' => array($vendorDir . '/symfony/stopwatch'), + 'Symfony\\Component\\Security\\' => array($vendorDir . '/symfony/security'), + 'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'), + 'Symfony\\Component\\PropertyAccess\\' => array($vendorDir . '/symfony/property-access'), + 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), + 'Symfony\\Component\\OptionsResolver\\' => array($vendorDir . '/symfony/options-resolver'), + 'Symfony\\Component\\Intl\\' => array($vendorDir . '/symfony/intl'), + 'Symfony\\Component\\Inflector\\' => array($vendorDir . '/symfony/inflector'), + 'Symfony\\Component\\HttpKernel\\' => array($vendorDir . '/symfony/http-kernel'), + 'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'), + 'Symfony\\Component\\Form\\' => array($vendorDir . '/symfony/form'), + 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), + 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), + 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), + 'Symfony\\Component\\DomCrawler\\' => array($vendorDir . '/symfony/dom-crawler'), + 'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'), + 'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'), + 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), + 'Symfony\\Component\\Config\\' => array($vendorDir . '/symfony/config'), + 'Symfony\\Component\\ClassLoader\\' => array($vendorDir . '/symfony/class-loader'), + 'Symfony\\Component\\BrowserKit\\' => array($vendorDir . '/symfony/browser-kit'), + 'Symfony\\Component\\Asset\\' => array($vendorDir . '/symfony/asset'), + 'Symfony\\Bundle\\WebProfilerBundle\\' => array($vendorDir . '/symfony/web-profiler-bundle'), + 'Symfony\\Bridge\\Twig\\' => array($vendorDir . '/symfony/twig-bridge'), + 'Symfony\\Bridge\\Monolog\\' => array($vendorDir . '/symfony/monolog-bridge'), + 'Silex\\Provider\\' => array($vendorDir . '/silex/web-profiler'), + 'Silex\\' => array($vendorDir . '/silex/silex/src/Silex'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'), + 'Doctrine\\Common\\Cache\\' => array($vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache'), + 'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'), + 'Doctrine\\Common\\' => array($vendorDir . '/doctrine/common/lib/Doctrine/Common'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 00000000..34ed5803 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInite4d062fdb9ca513610a11fb1e97e6e9b::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInite4d062fdb9ca513610a11fb1e97e6e9b::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequiree4d062fdb9ca513610a11fb1e97e6e9b($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequiree4d062fdb9ca513610a11fb1e97e6e9b($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 00000000..d4ba7197 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,303 @@ + __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php', + '023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php', + '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', + '6a47392539ca2329373e0d33e1dba053' => __DIR__ . '/..' . '/symfony/polyfill-intl-icu/bootstrap.php', + 'bd9634f2d41831496de0d3dfe4c94881' => __DIR__ . '/..' . '/symfony/polyfill-php56/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'T' => + array ( + 'Twig\\' => 5, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Util\\' => 22, + 'Symfony\\Polyfill\\Php70\\' => 23, + 'Symfony\\Polyfill\\Php56\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Component\\VarDumper\\' => 28, + 'Symfony\\Component\\Validator\\' => 28, + 'Symfony\\Component\\Translation\\' => 30, + 'Symfony\\Component\\Stopwatch\\' => 28, + 'Symfony\\Component\\Security\\' => 27, + 'Symfony\\Component\\Routing\\' => 26, + 'Symfony\\Component\\PropertyAccess\\' => 33, + 'Symfony\\Component\\Process\\' => 26, + 'Symfony\\Component\\OptionsResolver\\' => 34, + 'Symfony\\Component\\Intl\\' => 23, + 'Symfony\\Component\\Inflector\\' => 28, + 'Symfony\\Component\\HttpKernel\\' => 29, + 'Symfony\\Component\\HttpFoundation\\' => 33, + 'Symfony\\Component\\Form\\' => 23, + 'Symfony\\Component\\Finder\\' => 25, + 'Symfony\\Component\\Filesystem\\' => 29, + 'Symfony\\Component\\EventDispatcher\\' => 34, + 'Symfony\\Component\\DomCrawler\\' => 29, + 'Symfony\\Component\\Debug\\' => 24, + 'Symfony\\Component\\CssSelector\\' => 30, + 'Symfony\\Component\\Console\\' => 26, + 'Symfony\\Component\\Config\\' => 25, + 'Symfony\\Component\\ClassLoader\\' => 30, + 'Symfony\\Component\\BrowserKit\\' => 29, + 'Symfony\\Component\\Asset\\' => 24, + 'Symfony\\Bundle\\WebProfilerBundle\\' => 33, + 'Symfony\\Bridge\\Twig\\' => 20, + 'Symfony\\Bridge\\Monolog\\' => 23, + 'Silex\\Provider\\' => 15, + 'Silex\\' => 6, + ), + 'P' => + array ( + 'Psr\\Log\\' => 8, + ), + 'M' => + array ( + 'Monolog\\' => 8, + ), + 'D' => + array ( + 'Doctrine\\Common\\Cache\\' => 22, + 'Doctrine\\Common\\Annotations\\' => 28, + 'Doctrine\\Common\\' => 16, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Twig\\' => + array ( + 0 => __DIR__ . '/..' . '/twig/twig/src', + ), + 'Symfony\\Polyfill\\Util\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-util', + ), + 'Symfony\\Polyfill\\Php70\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php70', + ), + 'Symfony\\Polyfill\\Php56\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php56', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Component\\VarDumper\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/var-dumper', + ), + 'Symfony\\Component\\Validator\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/validator', + ), + 'Symfony\\Component\\Translation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/translation', + ), + 'Symfony\\Component\\Stopwatch\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/stopwatch', + ), + 'Symfony\\Component\\Security\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/security', + ), + 'Symfony\\Component\\Routing\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/routing', + ), + 'Symfony\\Component\\PropertyAccess\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/property-access', + ), + 'Symfony\\Component\\Process\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/process', + ), + 'Symfony\\Component\\OptionsResolver\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/options-resolver', + ), + 'Symfony\\Component\\Intl\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/intl', + ), + 'Symfony\\Component\\Inflector\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/inflector', + ), + 'Symfony\\Component\\HttpKernel\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-kernel', + ), + 'Symfony\\Component\\HttpFoundation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-foundation', + ), + 'Symfony\\Component\\Form\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/form', + ), + 'Symfony\\Component\\Finder\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/finder', + ), + 'Symfony\\Component\\Filesystem\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/filesystem', + ), + 'Symfony\\Component\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', + ), + 'Symfony\\Component\\DomCrawler\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/dom-crawler', + ), + 'Symfony\\Component\\Debug\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/debug', + ), + 'Symfony\\Component\\CssSelector\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/css-selector', + ), + 'Symfony\\Component\\Console\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/console', + ), + 'Symfony\\Component\\Config\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/config', + ), + 'Symfony\\Component\\ClassLoader\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/class-loader', + ), + 'Symfony\\Component\\BrowserKit\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/browser-kit', + ), + 'Symfony\\Component\\Asset\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/asset', + ), + 'Symfony\\Bundle\\WebProfilerBundle\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/web-profiler-bundle', + ), + 'Symfony\\Bridge\\Twig\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/twig-bridge', + ), + 'Symfony\\Bridge\\Monolog\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/monolog-bridge', + ), + 'Silex\\Provider\\' => + array ( + 0 => __DIR__ . '/..' . '/silex/web-profiler', + ), + 'Silex\\' => + array ( + 0 => __DIR__ . '/..' . '/silex/silex/src/Silex', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + 'Monolog\\' => + array ( + 0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog', + ), + 'Doctrine\\Common\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache', + ), + 'Doctrine\\Common\\Annotations\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations', + ), + 'Doctrine\\Common\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/common/lib/Doctrine/Common', + ), + ); + + public static $prefixesPsr0 = array ( + 'T' => + array ( + 'Twig_' => + array ( + 0 => __DIR__ . '/..' . '/twig/twig/lib', + ), + ), + 'P' => + array ( + 'Pimple' => + array ( + 0 => __DIR__ . '/..' . '/pimple/pimple/src', + ), + ), + 'D' => + array ( + 'Doctrine\\DBAL\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/dbal/lib', + ), + 'Doctrine\\Common\\Lexer\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/lexer/lib', + ), + 'Doctrine\\Common\\Inflector\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/inflector/lib', + ), + 'Doctrine\\Common\\Collections\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/collections/lib', + ), + ), + ); + + public static $fallbackDirsPsr0 = array ( + 0 => __DIR__ . '/../..' . '/src', + ); + + public static $classMap = array ( + 'ArithmeticError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php', + 'AssertionError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php', + 'Collator' => __DIR__ . '/..' . '/symfony/intl/Resources/stubs/Collator.php', + 'DivisionByZeroError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php', + 'Error' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/Error.php', + 'IntlDateFormatter' => __DIR__ . '/..' . '/symfony/intl/Resources/stubs/IntlDateFormatter.php', + 'Locale' => __DIR__ . '/..' . '/symfony/intl/Resources/stubs/Locale.php', + 'NumberFormatter' => __DIR__ . '/..' . '/symfony/intl/Resources/stubs/NumberFormatter.php', + 'ParseError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ParseError.php', + 'TypeError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/TypeError.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInite4d062fdb9ca513610a11fb1e97e6e9b::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInite4d062fdb9ca513610a11fb1e97e6e9b::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInite4d062fdb9ca513610a11fb1e97e6e9b::$prefixesPsr0; + $loader->fallbackDirsPsr0 = ComposerStaticInite4d062fdb9ca513610a11fb1e97e6e9b::$fallbackDirsPsr0; + $loader->classMap = ComposerStaticInite4d062fdb9ca513610a11fb1e97e6e9b::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 00000000..dbd3b61d --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,3055 @@ +[ + { + "name": "symfony/stopwatch", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "602a15299dc01556013b07167d4f5d3a60e90d15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/602a15299dc01556013b07167d4f5d3a60e90d15", + "reference": "602a15299dc01556013b07167d4f5d3a60e90d15", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "time": "2017-04-12T14:14:56+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2016-11-14T01:06:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "twig/twig", + "version": "v2.4.3", + "version_normalized": "2.4.3.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "eab7c3288ae6603d7d6f92b531626af2b162d1f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/eab7c3288ae6603d7d6f92b531626af2b162d1f2", + "reference": "eab7c3288ae6603d7d6f92b531626af2b162d1f2", + "shasum": "" + }, + "require": { + "php": "^7.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/debug": "~2.7", + "symfony/phpunit-bridge": "~3.3@dev" + }, + "time": "2017-06-07T18:47:58+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Twig_": "lib/" + }, + "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": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + }, + { + "name": "Twig Team", + "homepage": "http://twig.sensiolabs.org/contributors", + "role": "Contributors" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "http://twig.sensiolabs.org", + "keywords": [ + "templating" + ] + }, + { + "name": "symfony/twig-bridge", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "7f1bc6676396441dc938dc4dacb49ae91f56b930" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/7f1bc6676396441dc938dc4dacb49ae91f56b930", + "reference": "7f1bc6676396441dc938dc4dacb49ae91f56b930", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "twig/twig": "~1.34|~2.4" + }, + "require-dev": { + "fig/link-util": "^1.0", + "symfony/asset": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/form": "^3.2.7", + "symfony/http-kernel": "~3.2", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/routing": "~2.8|~3.0", + "symfony/security": "~2.8|~3.0", + "symfony/security-acl": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~2.8.10|~3.1.4|~3.2", + "symfony/web-link": "~3.3", + "symfony/yaml": "~2.8|~3.0" + }, + "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": "For using the SecurityExtension", + "symfony/stopwatch": "For using the StopwatchExtension", + "symfony/templating": "For using the TwigEngine", + "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" + }, + "time": "2017-06-06T03:13:52+00:00", + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/var-dumper", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "347c4247a3e40018810b476fcd5dec36d46d08dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/347c4247a3e40018810b476fcd5dec36d46d08dc", + "reference": "347c4247a3e40018810b476fcd5dec36d46d08dc", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, + "require-dev": { + "ext-iconv": "*", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-symfony_debug": "" + }, + "time": "2017-06-02T09:10:29+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "symfony/routing", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "39804eeafea5cca851946e1eed122eb94459fdb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/39804eeafea5cca851946e1eed122eb94459fdb4", + "reference": "39804eeafea5cca851946e1eed122eb94459fdb4", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.3" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/yaml": "~3.3" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/dependency-injection": "For loading routes from a service", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "time": "2017-06-02T09:51:43+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.10", + "version_normalized": "2.0.10.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d", + "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "time": "2017-03-13T16:27:32+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ] + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "13ce343935f0f91ca89605a2f6ca6f5c2f3faac2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/13ce343935f0f91ca89605a2f6ca6f5c2f3faac2", + "reference": "13ce343935f0f91ca89605a2f6ca6f5c2f3faac2", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0", + "php": ">=5.3.3" + }, + "time": "2016-11-14T01:06:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "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.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "psr/log", + "version": "1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-10-10T12:19:37+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "symfony/debug", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/e9c50482841ef696e8fa1470d950a79c8921f45d", + "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0" + }, + "time": "2017-06-01T21:01:25+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "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 Debug Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/http-foundation", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/80eb5a1f968448b77da9e8b2c0827f6e8d767846", + "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0" + }, + "time": "2017-06-05T13:06:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "4054a102470665451108f9b59305c79176ef98f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4054a102470665451108f9b59305c79176ef98f0", + "reference": "4054a102470665451108f9b59305c79176ef98f0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "time": "2017-06-04T18:15:29+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/http-kernel", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "be8280f7fa8e95b86514f1e1be997668a53b2888" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/be8280f7fa8e95b86514f1e1be997668a53b2888", + "reference": "be8280f7fa8e95b86514f1e1be997668a53b2888", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0", + "symfony/debug": "~2.8|~3.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "~3.3" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/var-dumper": "<3.3", + "twig/twig": "<1.34|<2.4,>=2" + }, + "require-dev": { + "psr/cache": "~1.0", + "symfony/browser-kit": "~2.8|~3.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/css-selector": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/dom-crawler": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0", + "symfony/routing": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~3.3" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "time": "2017-06-06T03:59:58+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/web-profiler-bundle", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/web-profiler-bundle.git", + "reference": "77a0722c855f1b2f44bdbd043935ccaf50fb8bc5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/77a0722c855f1b2f44bdbd043935ccaf50fb8bc5", + "reference": "77a0722c855f1b2f44bdbd043935ccaf50fb8bc5", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/http-kernel": "~3.2", + "symfony/polyfill-php70": "~1.0", + "symfony/routing": "~2.8|~3.0", + "symfony/twig-bridge": "~2.8|~3.0", + "symfony/var-dumper": "~3.3", + "twig/twig": "~1.34|~2.4" + }, + "conflict": { + "symfony/dependency-injection": "<3.3", + "symfony/event-dispatcher": "<3.2", + "symfony/var-dumper": "<3.3" + }, + "require-dev": { + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/stopwatch": "~2.8|~3.0" + }, + "time": "2017-06-03T11:15:34+00:00", + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "pimple/pimple", + "version": "v3.0.2", + "version_normalized": "3.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2015-09-11T15:10:35+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ] + }, + { + "name": "silex/silex", + "version": "v2.1.0", + "version_normalized": "2.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Silex.git", + "reference": "d5a9d9af14a1424ddecc3da481769cf64e7d3b34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Silex/zipball/d5a9d9af14a1424ddecc3da481769cf64e7d3b34", + "reference": "d5a9d9af14a1424ddecc3da481769cf64e7d3b34", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0", + "symfony/event-dispatcher": "~2.8|^3.0", + "symfony/http-foundation": "~2.8|^3.0", + "symfony/http-kernel": "~2.8|^3.0", + "symfony/routing": "~2.8|^3.0" + }, + "replace": { + "silex/api": "self.version", + "silex/providers": "self.version" + }, + "require-dev": { + "doctrine/dbal": "~2.2", + "monolog/monolog": "^1.4.1", + "swiftmailer/swiftmailer": "~5", + "symfony/asset": "~2.8|^3.0", + "symfony/browser-kit": "~2.8|^3.0", + "symfony/config": "~2.8|^3.0", + "symfony/css-selector": "~2.8|^3.0", + "symfony/debug": "~2.8|^3.0", + "symfony/doctrine-bridge": "~2.8|^3.0", + "symfony/dom-crawler": "~2.8|^3.0", + "symfony/expression-language": "~2.8|^3.0", + "symfony/finder": "~2.8|^3.0", + "symfony/form": "~2.8|^3.0", + "symfony/intl": "~2.8|^3.0", + "symfony/monolog-bridge": "~2.8|^3.0", + "symfony/options-resolver": "~2.8|^3.0", + "symfony/phpunit-bridge": "^3.2", + "symfony/process": "~2.8|^3.0", + "symfony/security": "~2.8|^3.0", + "symfony/serializer": "~2.8|^3.0", + "symfony/translation": "~2.8|^3.0", + "symfony/twig-bridge": "~2.8|^3.0", + "symfony/validator": "~2.8|^3.0", + "symfony/var-dumper": "~2.8|^3.0", + "symfony/web-link": "^3.3", + "twig/twig": "~1.28|~2.0" + }, + "time": "2017-05-03T15:21:42+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Silex\\": "src/Silex" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "The PHP micro-framework based on the Symfony Components", + "homepage": "http://silex.sensiolabs.org", + "keywords": [ + "microframework" + ] + }, + { + "name": "silex/web-profiler", + "version": "v2.0.9", + "version_normalized": "2.0.9.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Silex-WebProfiler.git", + "reference": "cd76e55490d556a81378b8230e28f55536811418" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Silex-WebProfiler/zipball/cd76e55490d556a81378b8230e28f55536811418", + "reference": "cd76e55490d556a81378b8230e28f55536811418", + "shasum": "" + }, + "require": { + "silex/silex": "^2.0", + "symfony/stopwatch": "^2.8|^3.0", + "symfony/twig-bridge": "^2.8|^3.0", + "symfony/web-profiler-bundle": "^2.8|^3.0" + }, + "conflict": { + "symfony/web-profiler-bundle": "3.1.0" + }, + "require-dev": { + "symfony/browser-kit": "^2.8|^3.0", + "symfony/css-selector": "^2.8|^3.0", + "symfony/debug-bundle": "^2.8|^3.0", + "symfony/phpunit-bridge": "~3.2", + "symfony/security": "^2.8|^3.0", + "symfony/security-bundle": "^2.8|^3.0", + "symfony/translation": "^2.8|^3.0" + }, + "time": "2017-04-23T23:11:39+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Silex\\Provider\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A WebProfiler for Silex", + "homepage": "http://silex.sensiolabs.org/" + }, + { + "name": "symfony/asset", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset.git", + "reference": "67ee69a32a4abd97bb14f938e51e714fbb38555b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset/zipball/67ee69a32a4abd97bb14f938e51e714fbb38555b", + "reference": "67ee69a32a4abd97bb14f938e51e714fbb38555b", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/http-foundation": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0" + }, + "suggest": { + "symfony/http-foundation": "" + }, + "time": "2017-05-01T15:01:29+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/dom-crawler", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "fc2c588ce376e9fe04a7b8c79e3ec62fe32d95b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/fc2c588ce376e9fe04a7b8c79e3ec62fe32d95b1", + "reference": "fc2c588ce376e9fe04a7b8c79e3ec62fe32d95b1", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "time": "2017-05-25T23:10:31+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/browser-kit", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "c2c8ceb1aa9dab9eae54e9150e6a588ce3e53be1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/c2c8ceb1aa9dab9eae54e9150e6a588ce3e53be1", + "reference": "c2c8ceb1aa9dab9eae54e9150e6a588ce3e53be1", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/dom-crawler": "~2.8|~3.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "symfony/process": "" + }, + "time": "2017-04-12T14:14:56+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/class-loader", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/class-loader.git", + "reference": "386a294d621576302e7cc36965d6ed53b8c73c4f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/386a294d621576302e7cc36965d6ed53b8c73c4f", + "reference": "386a294d621576302e7cc36965d6ed53b8c73c4f", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/finder": "~2.8|~3.0", + "symfony/polyfill-apcu": "~1.1" + }, + "suggest": { + "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" + }, + "time": "2017-06-02T09:51:43+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\ClassLoader\\": "" + }, + "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 ClassLoader Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/filesystem", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "c709670bf64721202ddbe4162846f250735842c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/c709670bf64721202ddbe4162846f250735842c0", + "reference": "c709670bf64721202ddbe4162846f250735842c0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "time": "2017-05-28T14:08:56+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/config", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "35716d4904e0506a7a5a9bcf23f854aeb5719bca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/35716d4904e0506a7a5a9bcf23f854aeb5719bca", + "reference": "35716d4904e0506a7a5a9bcf23f854aeb5719bca", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/filesystem": "~2.8|~3.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "symfony/dependency-injection": "~3.3", + "symfony/yaml": "~3.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "time": "2017-06-02T18:07:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/console", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "70d2a29b2911cbdc91a7e268046c395278238b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/70d2a29b2911cbdc91a7e268046c395278238b2e", + "reference": "70d2a29b2911cbdc91a7e268046c395278238b2e", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/debug": "~2.8|~3.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.3", + "symfony/dependency-injection": "~3.3", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/filesystem": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/filesystem": "", + "symfony/process": "" + }, + "time": "2017-06-02T19:24:58+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/css-selector", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "4d882dced7b995d5274293039370148e291808f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/4d882dced7b995d5274293039370148e291808f2", + "reference": "4d882dced7b995d5274293039370148e291808f2", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "time": "2017-05-01T15:01:29+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/finder", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4", + "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "time": "2017-06-01T21:01:25+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/inflector", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/inflector.git", + "reference": "aed5a0874a3bcfd8d0393a2d91b4cf828f29c7fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/inflector/zipball/aed5a0874a3bcfd8d0393a2d91b4cf828f29c7fb", + "reference": "aed5a0874a3bcfd8d0393a2d91b4cf828f29c7fb", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "time": "2017-04-12T14:14:56+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "symfony/property-access", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-access.git", + "reference": "70bbfe28f77070d41957495bc6fed34f139fe6f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-access/zipball/70bbfe28f77070d41957495bc6fed34f139fe6f7", + "reference": "70bbfe28f77070d41957495bc6fed34f139fe6f7", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/inflector": "~3.1", + "symfony/polyfill-php70": "~1.0" + }, + "require-dev": { + "symfony/cache": "~3.1" + }, + "suggest": { + "psr/cache-implementation": "To cache access methods." + }, + "time": "2017-06-02T09:51:43+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "symfony/options-resolver", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/ff48982d295bcac1fd861f934f041ebc73ae40f0", + "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "time": "2017-04-12T14:14:56+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "symfony/intl", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/intl.git", + "reference": "a310b29a794578f544305f4f48e3f6c7981b606c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/intl/zipball/a310b29a794578f544305f4f48e3f6c7981b606c", + "reference": "a310b29a794578f544305f4f48e3f6c7981b606c", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-intl-icu": "~1.0" + }, + "require-dev": { + "symfony/filesystem": "~2.8|~3.0" + }, + "suggest": { + "ext-intl": "to use the component with locales other than \"en\"" + }, + "time": "2017-06-01T21:01:25+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "symfony/polyfill-intl-icu", + "version": "v1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-icu.git", + "reference": "2d6e2b20d457603eefb6e614286c22efca30fdb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/2d6e2b20d457603eefb6e614286c22efca30fdb4", + "reference": "2d6e2b20d457603eefb6e614286c22efca30fdb4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/intl": "~2.3|~3.0" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2016-11-14T01:06:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "symfony/form", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/form.git", + "reference": "585d656096e87f0ee1443dbeeb9bc471bcc858cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/form/zipball/585d656096e87f0ee1443dbeeb9bc471bcc858cd", + "reference": "585d656096e87f0ee1443dbeeb9bc471bcc858cd", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/intl": "^2.8.18|^3.2.5", + "symfony/options-resolver": "~2.8|~3.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/property-access": "~2.8|~3.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/dependency-injection": "<3.3", + "symfony/doctrine-bridge": "<2.7", + "symfony/framework-bundle": "<2.7", + "symfony/twig-bridge": "<2.7", + "symfony/var-dumper": "<3.3" + }, + "require-dev": { + "doctrine/collections": "~1.0", + "symfony/config": "~2.7|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0", + "symfony/security-csrf": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/validator": "^2.8.18|^3.2.5", + "symfony/var-dumper": "~3.3" + }, + "suggest": { + "symfony/framework-bundle": "For templating with PHP.", + "symfony/security-csrf": "For protecting forms against CSRF attacks.", + "symfony/twig-bridge": "For templating with Twig.", + "symfony/validator": "For form validation." + }, + "time": "2017-06-06T03:13:52+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "monolog/monolog", + "version": "1.22.1", + "version_normalized": "1.22.1.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1e044bc4b34e91743943479f1be7a1d5eb93add0", + "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "~5.3" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "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", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "time": "2017-03-13T07:08:03+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "symfony/monolog-bridge", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bridge.git", + "reference": "c659d3a02eb39c8e524bf5933620f9f83d9a4167" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/c659d3a02eb39c8e524bf5933620f9f83d9a4167", + "reference": "c659d3a02eb39c8e524bf5933620f9f83d9a4167", + "shasum": "" + }, + "require": { + "monolog/monolog": "~1.19", + "php": ">=5.5.9", + "symfony/http-kernel": "~2.8|~3.0" + }, + "conflict": { + "symfony/http-foundation": "<3.3" + }, + "require-dev": { + "symfony/console": "~2.8|~3.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/var-dumper": "~3.3" + }, + "suggest": { + "symfony/console": "For the possibility to show log messages in console commands depending on verbosity settings. You need version ~2.3 of the console for it.", + "symfony/event-dispatcher": "Needed when using log messages in console commands.", + "symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel." + }, + "time": "2017-05-20T07:30:49+00:00", + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/process", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "8e30690c67aafb6c7992d6d8eb0d707807dd3eaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/8e30690c67aafb6c7992d6d8eb0d707807dd3eaf", + "reference": "8e30690c67aafb6c7992d6d8eb0d707807dd3eaf", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "time": "2017-05-22T12:32:03+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "746bce0fca664ac0a575e465f65c6643faddf7fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/746bce0fca664ac0a575e465f65c6643faddf7fb", + "reference": "746bce0fca664ac0a575e465f65c6643faddf7fb", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2016-11-14T01:06:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "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 utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ] + }, + { + "name": "symfony/polyfill-php56", + "version": "v1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/1dd42b9b89556f18092f3d1ada22cb05ac85383c", + "reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "time": "2016-11-14T01:06:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "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 backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/security", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/security.git", + "reference": "b92647f30b03f9d749c9343b39acef7e28bf3f9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security/zipball/b92647f30b03f9d749c9343b39acef7e28bf3f9a", + "reference": "b92647f30b03f9d749c9343b39acef7e28bf3f9a", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/http-kernel": "~3.3", + "symfony/polyfill-php56": "~1.0", + "symfony/polyfill-php70": "~1.0", + "symfony/polyfill-util": "~1.0", + "symfony/property-access": "~2.8|~3.0" + }, + "replace": { + "symfony/security-core": "self.version", + "symfony/security-csrf": "self.version", + "symfony/security-guard": "self.version", + "symfony/security-http": "self.version" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/ldap": "~3.1", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/routing": "~2.8|~3.0", + "symfony/validator": "^2.8.18|^3.2.5" + }, + "suggest": { + "symfony/expression-language": "For using the expression voter", + "symfony/form": "", + "symfony/ldap": "For using the LDAP user and authentication providers", + "symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs", + "symfony/validator": "For using the user password constraint" + }, + "time": "2017-06-03T08:00:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\": "" + }, + "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", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/translation", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/dc3b2a0c6cfff60327ba1c043a82092735397543", + "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/yaml": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/intl": "^2.8.18|^3.2.5", + "symfony/yaml": "~3.3" + }, + "suggest": { + "psr/log": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "time": "2017-05-22T07:42:36+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "symfony/validator", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/validator.git", + "reference": "07888aa5384e3a6cbdb29d158f5aaf99f67e1b88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/validator/zipball/07888aa5384e3a6cbdb29d158f5aaf99f67e1b88", + "reference": "07888aa5384e3a6cbdb29d158f5aaf99f67e1b88", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation": "~2.8|~3.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.3" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/cache": "~1.0", + "egulias/email-validator": "^1.2.8|~2.0", + "symfony/cache": "~3.1", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/intl": "^2.8.18|^3.2.5", + "symfony/yaml": "~3.3" + }, + "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.", + "egulias/email-validator": "Strict (RFC compliant) email validation", + "psr/cache-implementation": "For using the metadata cache.", + "symfony/config": "", + "symfony/expression-language": "For using the Expression validator", + "symfony/http-foundation": "", + "symfony/intl": "", + "symfony/property-access": "For using the Expression validator", + "symfony/yaml": "" + }, + "time": "2017-06-02T14:38:05+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "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" + }, + { + "name": "doctrine/lexer", + "version": "v1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "time": "2014-09-09T13:34:57+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ] + }, + { + "name": "doctrine/inflector", + "version": "v1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "time": "2015-11-06T14:35:42+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Inflector\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "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" + ] + }, + { + "name": "doctrine/collections", + "version": "v1.4.0", + "version_normalized": "1.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "~0.1@dev", + "phpunit/phpunit": "^5.7" + }, + "time": "2017-01-03T10:49:41+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "array", + "collections", + "iterator" + ] + }, + { + "name": "doctrine/cache", + "version": "v1.6.1", + "version_normalized": "1.6.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/b6f544a20f4807e81f7044d31e679ccbb1866dc3", + "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3", + "shasum": "" + }, + "require": { + "php": "~5.5|~7.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "phpunit/phpunit": "~4.8|~5.0", + "predis/predis": "~1.0", + "satooshi/php-coveralls": "~0.6" + }, + "time": "2016-10-29T11:16:17+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ] + }, + { + "name": "doctrine/annotations", + "version": "v1.4.0", + "version_normalized": "1.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^5.7" + }, + "time": "2017-02-24T16:22:25+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "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" + ] + }, + { + "name": "doctrine/common", + "version": "v2.7.2", + "version_normalized": "2.7.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "930297026c8009a567ac051fd545bf6124150347" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/930297026c8009a567ac051fd545bf6124150347", + "reference": "930297026c8009a567ac051fd545bf6124150347", + "shasum": "" + }, + "require": { + "doctrine/annotations": "1.*", + "doctrine/cache": "1.*", + "doctrine/collections": "1.*", + "doctrine/inflector": "1.*", + "doctrine/lexer": "1.*", + "php": "~5.6|~7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4.6" + }, + "time": "2017-01-13T14:02:13+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common Library for Doctrine projects", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "collections", + "eventmanager", + "persistence", + "spl" + ] + }, + { + "name": "doctrine/dbal", + "version": "v2.5.12", + "version_normalized": "2.5.12.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "7b9e911f9d8b30d43b96853dab26898c710d8f44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/7b9e911f9d8b30d43b96853dab26898c710d8f44", + "reference": "7b9e911f9d8b30d43b96853dab26898c710d8f44", + "shasum": "" + }, + "require": { + "doctrine/common": ">=2.4,<2.8-dev", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*", + "symfony/console": "2.*||^3.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "time": "2017-02-08T12:53:47+00:00", + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\DBAL\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Database Abstraction Layer", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "persistence", + "queryobject" + ] + } +] diff --git a/vendor/doctrine/annotations/CHANGELOG.md b/vendor/doctrine/annotations/CHANGELOG.md new file mode 100644 index 00000000..c09ebe60 --- /dev/null +++ b/vendor/doctrine/annotations/CHANGELOG.md @@ -0,0 +1,115 @@ +## Changelog + + +### 1.4.0 + +This release fix an issue were some annotations could be not loaded if the namespace in the use statement started with a backslash. +It also update the tests and drop the support for php 5.X + +- [115: Missing annotations with the latest composer version](https://github.com/doctrine/annotations/issues/115) thanks to @pascalporedda +- [120: Missing annotations with the latest composer version](https://github.com/doctrine/annotations/pull/120) thanks to @gnat42 +- [121: Adding a more detailed explanation of the test](https://github.com/doctrine/annotations/pull/121) thanks to @mikeSimonson +- [101: Test annotation parameters containing space](https://github.com/doctrine/annotations/pull/101) thanks to @mikeSimonson +- [111: Cleanup: move to correct phpunit assertions](https://github.com/doctrine/annotations/pull/111) thanks to @Ocramius +- [112: Removes support for PHP 5.x](https://github.com/doctrine/annotations/pull/112) thanks to @railto +- [113: bumped phpunit version to 5.7](https://github.com/doctrine/annotations/pull/113) thanks to @gabbydgab +- [114: Enhancement: Use SVG Travis build badge](https://github.com/doctrine/annotations/pull/114) thanks to @localheinz +- [118: Integrating PHPStan](https://github.com/doctrine/annotations/pull/118) thanks to @ondrejmirtes + +### 1.3.1 - 2016-12-30 + +This release fixes an issue with ignored annotations that were already +autoloaded, causing the `SimpleAnnotationReader` to pick them up +anyway. [#110](https://github.com/doctrine/annotations/pull/110) + +Additionally, an issue was fixed in the `CachedReader`, which was +not correctly checking the freshness of cached annotations when +traits were defined on a class. [#105](https://github.com/doctrine/annotations/pull/105) + +Total issues resolved: **2** + +- [105: Return single max timestamp](https://github.com/doctrine/annotations/pull/105) +- [110: setIgnoreNotImportedAnnotations(true) didn’t work for existing classes](https://github.com/doctrine/annotations/pull/110) + +### 1.3.0 + +This release introduces a PHP version bump. `doctrine/annotations` now requires PHP +5.6 or later to be installed. + +A series of additional improvements have been introduced: + + * support for PHP 7 "grouped use statements" + * support for ignoring entire namespace names + via `Doctrine\Common\Annotations\AnnotationReader::addGlobalIgnoredNamespace()` and + `Doctrine\Common\Annotations\DocParser::setIgnoredAnnotationNamespaces()`. This will + allow you to ignore annotations from namespaces that you cannot autoload + * testing all parent classes and interfaces when checking if the annotation cache + in the `CachedReader` is fresh + * simplifying the cache keys used by the `CachedReader`: keys are no longer artificially + namespaced, since `Doctrine\Common\Cache` already supports that + * corrected parsing of multibyte strings when `mbstring.func_overload` is enabled + * corrected parsing of annotations when `"\t"` is put before the first annotation + in a docblock + * allow skipping non-imported annotations when a custom `DocParser` is passed to + the `AnnotationReader` constructor + +Total issues resolved: **15** + +- [45: DocParser can now ignore whole namespaces](https://github.com/doctrine/annotations/pull/45) +- [57: Switch to the docker-based infrastructure on Travis](https://github.com/doctrine/annotations/pull/57) +- [59: opcache.load_comments has been removed from PHP 7](https://github.com/doctrine/annotations/pull/59) +- [62: [CachedReader\ Test traits and parent class to see if cache is fresh](https://github.com/doctrine/annotations/pull/62) +- [65: Remove cache salt making key unnecessarily long](https://github.com/doctrine/annotations/pull/65) +- [66: Fix of incorrect parsing multibyte strings](https://github.com/doctrine/annotations/pull/66) +- [68: Annotations that are indented by tab are not processed.](https://github.com/doctrine/annotations/issues/68) +- [69: Support for Group Use Statements](https://github.com/doctrine/annotations/pull/69) +- [70: Allow tab character before first annotation in DocBlock](https://github.com/doctrine/annotations/pull/70) +- [74: Ignore not registered annotations fix](https://github.com/doctrine/annotations/pull/74) +- [92: Added tests for AnnotationRegistry class.](https://github.com/doctrine/annotations/pull/92) +- [96: Fix/#62 check trait and parent class ttl in annotations](https://github.com/doctrine/annotations/pull/96) +- [97: Feature - #45 - allow ignoring entire namespaces](https://github.com/doctrine/annotations/pull/97) +- [98: Enhancement/#65 remove cache salt from cached reader](https://github.com/doctrine/annotations/pull/98) +- [99: Fix - #70 - allow tab character before first annotation in docblock](https://github.com/doctrine/annotations/pull/99) + +### 1.2.4 + +Total issues resolved: **1** + +- [51: FileCacheReader::saveCacheFile::unlink fix](https://github.com/doctrine/annotations/pull/51) + +### 1.2.3 + +Total issues resolved: [**2**](https://github.com/doctrine/annotations/milestones/v1.2.3) + +- [49: #46 - applying correct `chmod()` to generated cache file](https://github.com/doctrine/annotations/pull/49) +- [50: Hotfix: match escaped quotes (revert #44)](https://github.com/doctrine/annotations/pull/50) + +### 1.2.2 + +Total issues resolved: **4** + +- [43: Exclude files from distribution with .gitattributes](https://github.com/doctrine/annotations/pull/43) +- [44: Update DocLexer.php](https://github.com/doctrine/annotations/pull/44) +- [46: A plain "file_put_contents" can cause havoc](https://github.com/doctrine/annotations/pull/46) +- [48: Deprecating the `FileCacheReader` in 1.2.2: will be removed in 2.0.0](https://github.com/doctrine/annotations/pull/48) + +### 1.2.1 + +Total issues resolved: **4** + +- [38: fixes doctrine/common#326](https://github.com/doctrine/annotations/pull/38) +- [39: Remove superfluous NS](https://github.com/doctrine/annotations/pull/39) +- [41: Warn if load_comments is not enabled.](https://github.com/doctrine/annotations/pull/41) +- [42: Clean up unused uses](https://github.com/doctrine/annotations/pull/42) + +### 1.2.0 + + * HHVM support + * Allowing dangling comma in annotations + * Excluded annotations are no longer autoloaded + * Importing namespaces also in traits + * Added support for `::class` 5.5-style constant, works also in 5.3 and 5.4 + +### 1.1.0 + + * Add Exception when ZendOptimizer+ or Opcache is configured to drop comments diff --git a/vendor/doctrine/annotations/LICENSE b/vendor/doctrine/annotations/LICENSE new file mode 100644 index 00000000..5e781fce --- /dev/null +++ b/vendor/doctrine/annotations/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2013 Doctrine Project + +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/vendor/doctrine/annotations/README.md b/vendor/doctrine/annotations/README.md new file mode 100644 index 00000000..8f89ea54 --- /dev/null +++ b/vendor/doctrine/annotations/README.md @@ -0,0 +1,17 @@ +# Doctrine Annotations + +[![Build Status](https://travis-ci.org/doctrine/annotations.svg?branch=master)](https://travis-ci.org/doctrine/annotations) +[![Dependency Status](https://www.versioneye.com/package/php--doctrine--annotations/badge.png)](https://www.versioneye.com/package/php--doctrine--annotations) +[![Reference Status](https://www.versioneye.com/php/doctrine:annotations/reference_badge.svg)](https://www.versioneye.com/php/doctrine:annotations/references) +[![Total Downloads](https://poser.pugx.org/doctrine/annotations/downloads.png)](https://packagist.org/packages/doctrine/annotations) +[![Latest Stable Version](https://poser.pugx.org/doctrine/annotations/v/stable.png)](https://packagist.org/packages/doctrine/annotations) + +Docblock Annotations Parser library (extracted from [Doctrine Common](https://github.com/doctrine/common)). + +## Documentation + +See the [doctrine-project website](http://docs.doctrine-project.org/projects/doctrine-common/en/latest/reference/annotations.html). + +## Changelog + +See [CHANGELOG.md](CHANGELOG.md). diff --git a/vendor/doctrine/annotations/composer.json b/vendor/doctrine/annotations/composer.json new file mode 100644 index 00000000..6a2719b3 --- /dev/null +++ b/vendor/doctrine/annotations/composer.json @@ -0,0 +1,34 @@ +{ + "name": "doctrine/annotations", + "type": "library", + "description": "Docblock Annotations Parser", + "keywords": ["annotations", "docblock", "parser"], + "homepage": "http://www.doctrine-project.org", + "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"} + ], + "require": { + "php": "^5.6 || ^7.0", + "doctrine/lexer": "1.*" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^5.7" + }, + "autoload": { + "psr-4": { "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" } + }, + "autoload-dev": { + "psr-4": { "Doctrine\\Tests\\Common\\Annotations\\": "tests/Doctrine/Tests/Common/Annotations" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php new file mode 100644 index 00000000..a79a0f8f --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php @@ -0,0 +1,79 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Annotations class. + * + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class Annotation +{ + /** + * Value property. Common among all derived classes. + * + * @var string + */ + public $value; + + /** + * Constructor. + * + * @param array $data Key-value for properties to be defined in this class. + */ + public final function __construct(array $data) + { + foreach ($data as $key => $value) { + $this->$key = $value; + } + } + + /** + * Error handler for unknown property accessor in Annotation class. + * + * @param string $name Unknown property name. + * + * @throws \BadMethodCallException + */ + public function __get($name) + { + throw new \BadMethodCallException( + sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) + ); + } + + /** + * Error handler for unknown property mutator in Annotation class. + * + * @param string $name Unknown property name. + * @param mixed $value Property value. + * + * @throws \BadMethodCallException + */ + public function __set($name, $value) + { + throw new \BadMethodCallException( + sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) + ); + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php new file mode 100644 index 00000000..dbef6df0 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php @@ -0,0 +1,47 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser + * to check the attribute type during the parsing process. + * + * @author Fabio B. Silva + * + * @Annotation + */ +final class Attribute +{ + /** + * @var string + */ + public $name; + + /** + * @var string + */ + public $type; + + /** + * @var boolean + */ + public $required = false; +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php new file mode 100644 index 00000000..53134e30 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php @@ -0,0 +1,37 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser + * to check the types of all declared attributes during the parsing process. + * + * @author Fabio B. Silva + * + * @Annotation + */ +final class Attributes +{ + /** + * @var array + */ + public $value; +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php new file mode 100644 index 00000000..e122a753 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php @@ -0,0 +1,84 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser + * to check the available values during the parsing process. + * + * @since 2.4 + * @author Fabio B. Silva + * + * @Annotation + * @Attributes({ + * @Attribute("value", required = true, type = "array"), + * @Attribute("literal", required = false, type = "array") + * }) + */ +final class Enum +{ + /** + * @var array + */ + public $value; + + /** + * Literal target declaration. + * + * @var array + */ + public $literal; + + /** + * Annotation constructor. + * + * @param array $values + * + * @throws \InvalidArgumentException + */ + public function __construct(array $values) + { + if ( ! isset($values['literal'])) { + $values['literal'] = array(); + } + + foreach ($values['value'] as $var) { + if( ! is_scalar($var)) { + throw new \InvalidArgumentException(sprintf( + '@Enum supports only scalar values "%s" given.', + is_object($var) ? get_class($var) : gettype($var) + )); + } + } + + foreach ($values['literal'] as $key => $var) { + if( ! in_array($key, $values['value'])) { + throw new \InvalidArgumentException(sprintf( + 'Undefined enumerator value "%s" for literal "%s".', + $key , $var + )); + } + } + + $this->value = $values['value']; + $this->literal = $values['literal']; + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php new file mode 100644 index 00000000..175226a6 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php @@ -0,0 +1,54 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser to ignore specific + * annotations during the parsing process. + * + * @Annotation + * @author Johannes M. Schmitt + */ +final class IgnoreAnnotation +{ + /** + * @var array + */ + public $names; + + /** + * Constructor. + * + * @param array $values + * + * @throws \RuntimeException + */ + public function __construct(array $values) + { + if (is_string($values['value'])) { + $values['value'] = array($values['value']); + } + if (!is_array($values['value'])) { + throw new \RuntimeException(sprintf('@IgnoreAnnotation expects either a string name, or an array of strings, but got %s.', json_encode($values['value']))); + } + + $this->names = $values['value']; + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php new file mode 100644 index 00000000..d67f9606 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php @@ -0,0 +1,33 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser + * to check if that attribute is required during the parsing process. + * + * @author Fabio B. Silva + * + * @Annotation + */ +final class Required +{ +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php new file mode 100644 index 00000000..f6c54453 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php @@ -0,0 +1,107 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser + * to check the annotation target during the parsing process. + * + * @author Fabio B. Silva + * + * @Annotation + */ +final class Target +{ + const TARGET_CLASS = 1; + const TARGET_METHOD = 2; + const TARGET_PROPERTY = 4; + const TARGET_ANNOTATION = 8; + const TARGET_ALL = 15; + + /** + * @var array + */ + private static $map = array( + 'ALL' => self::TARGET_ALL, + 'CLASS' => self::TARGET_CLASS, + 'METHOD' => self::TARGET_METHOD, + 'PROPERTY' => self::TARGET_PROPERTY, + 'ANNOTATION' => self::TARGET_ANNOTATION, + ); + + /** + * @var array + */ + public $value; + + /** + * Targets as bitmask. + * + * @var integer + */ + public $targets; + + /** + * Literal target declaration. + * + * @var integer + */ + public $literal; + + /** + * Annotation constructor. + * + * @param array $values + * + * @throws \InvalidArgumentException + */ + public function __construct(array $values) + { + if (!isset($values['value'])){ + $values['value'] = null; + } + if (is_string($values['value'])){ + $values['value'] = array($values['value']); + } + if (!is_array($values['value'])){ + throw new \InvalidArgumentException( + sprintf('@Target expects either a string value, or an array of strings, "%s" given.', + is_object($values['value']) ? get_class($values['value']) : gettype($values['value']) + ) + ); + } + + $bitmask = 0; + foreach ($values['value'] as $literal) { + if(!isset(self::$map[$literal])){ + throw new \InvalidArgumentException( + sprintf('Invalid Target "%s". Available targets: [%s]', + $literal, implode(', ', array_keys(self::$map))) + ); + } + $bitmask |= self::$map[$literal]; + } + + $this->targets = $bitmask; + $this->value = $values['value']; + $this->literal = implode(', ', $this->value); + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php new file mode 100644 index 00000000..d06fe663 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php @@ -0,0 +1,197 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Description of AnnotationException + * + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class AnnotationException extends \Exception +{ + /** + * Creates a new AnnotationException describing a Syntax error. + * + * @param string $message Exception message + * + * @return AnnotationException + */ + public static function syntaxError($message) + { + return new self('[Syntax Error] ' . $message); + } + + /** + * Creates a new AnnotationException describing a Semantical error. + * + * @param string $message Exception message + * + * @return AnnotationException + */ + public static function semanticalError($message) + { + return new self('[Semantical Error] ' . $message); + } + + /** + * Creates a new AnnotationException describing an error which occurred during + * the creation of the annotation. + * + * @since 2.2 + * + * @param string $message + * + * @return AnnotationException + */ + public static function creationError($message) + { + return new self('[Creation Error] ' . $message); + } + + /** + * Creates a new AnnotationException describing a type error. + * + * @since 1.1 + * + * @param string $message + * + * @return AnnotationException + */ + public static function typeError($message) + { + return new self('[Type Error] ' . $message); + } + + /** + * Creates a new AnnotationException describing a constant semantical error. + * + * @since 2.3 + * + * @param string $identifier + * @param string $context + * + * @return AnnotationException + */ + public static function semanticalErrorConstants($identifier, $context = null) + { + return self::semanticalError(sprintf( + "Couldn't find constant %s%s.", + $identifier, + $context ? ', ' . $context : '' + )); + } + + /** + * Creates a new AnnotationException describing an type error of an attribute. + * + * @since 2.2 + * + * @param string $attributeName + * @param string $annotationName + * @param string $context + * @param string $expected + * @param mixed $actual + * + * @return AnnotationException + */ + public static function attributeTypeError($attributeName, $annotationName, $context, $expected, $actual) + { + return self::typeError(sprintf( + 'Attribute "%s" of @%s declared on %s expects %s, but got %s.', + $attributeName, + $annotationName, + $context, + $expected, + is_object($actual) ? 'an instance of ' . get_class($actual) : gettype($actual) + )); + } + + /** + * Creates a new AnnotationException describing an required error of an attribute. + * + * @since 2.2 + * + * @param string $attributeName + * @param string $annotationName + * @param string $context + * @param string $expected + * + * @return AnnotationException + */ + public static function requiredError($attributeName, $annotationName, $context, $expected) + { + return self::typeError(sprintf( + 'Attribute "%s" of @%s declared on %s expects %s. This value should not be null.', + $attributeName, + $annotationName, + $context, + $expected + )); + } + + /** + * Creates a new AnnotationException describing a invalid enummerator. + * + * @since 2.4 + * + * @param string $attributeName + * @param string $annotationName + * @param string $context + * @param array $available + * @param mixed $given + * + * @return AnnotationException + */ + public static function enumeratorError($attributeName, $annotationName, $context, $available, $given) + { + return new self(sprintf( + '[Enum Error] Attribute "%s" of @%s declared on %s accept only [%s], but got %s.', + $attributeName, + $annotationName, + $context, + implode(', ', $available), + is_object($given) ? get_class($given) : $given + )); + } + + /** + * @return AnnotationException + */ + public static function optimizerPlusSaveComments() + { + return new self( + "You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1." + ); + } + + /** + * @return AnnotationException + */ + public static function optimizerPlusLoadComments() + { + return new self( + "You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1." + ); + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php new file mode 100644 index 00000000..6c579775 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php @@ -0,0 +1,421 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use Doctrine\Common\Annotations\Annotation\IgnoreAnnotation; +use Doctrine\Common\Annotations\Annotation\Target; +use ReflectionClass; +use ReflectionMethod; +use ReflectionProperty; + +/** + * A reader for docblock annotations. + * + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Johannes M. Schmitt + */ +class AnnotationReader implements Reader +{ + /** + * Global map for imports. + * + * @var array + */ + private static $globalImports = array( + 'ignoreannotation' => 'Doctrine\Common\Annotations\Annotation\IgnoreAnnotation', + ); + + /** + * A list with annotations that are not causing exceptions when not resolved to an annotation class. + * + * The names are case sensitive. + * + * @var array + */ + private static $globalIgnoredNames = array( + // Annotation tags + 'Annotation' => true, 'Attribute' => true, 'Attributes' => true, + /* Can we enable this? 'Enum' => true, */ + 'Required' => true, + 'Target' => true, + // Widely used tags (but not existent in phpdoc) + 'fix' => true , 'fixme' => true, + 'override' => true, + // PHPDocumentor 1 tags + 'abstract'=> true, 'access'=> true, + 'code' => true, + 'deprec'=> true, + 'endcode' => true, 'exception'=> true, + 'final'=> true, + 'ingroup' => true, 'inheritdoc'=> true, 'inheritDoc'=> true, + 'magic' => true, + 'name'=> true, + 'toc' => true, 'tutorial'=> true, + 'private' => true, + 'static'=> true, 'staticvar'=> true, 'staticVar'=> true, + 'throw' => true, + // PHPDocumentor 2 tags. + 'api' => true, 'author'=> true, + 'category'=> true, 'copyright'=> true, + 'deprecated'=> true, + 'example'=> true, + 'filesource'=> true, + 'global'=> true, + 'ignore'=> true, /* Can we enable this? 'index' => true, */ 'internal'=> true, + 'license'=> true, 'link'=> true, + 'method' => true, + 'package'=> true, 'param'=> true, 'property' => true, 'property-read' => true, 'property-write' => true, + 'return'=> true, + 'see'=> true, 'since'=> true, 'source' => true, 'subpackage'=> true, + 'throws'=> true, 'todo'=> true, 'TODO'=> true, + 'usedby'=> true, 'uses' => true, + 'var'=> true, 'version'=> true, + // PHPUnit tags + 'codeCoverageIgnore' => true, 'codeCoverageIgnoreStart' => true, 'codeCoverageIgnoreEnd' => true, + // PHPCheckStyle + 'SuppressWarnings' => true, + // PHPStorm + 'noinspection' => true, + // PEAR + 'package_version' => true, + // PlantUML + 'startuml' => true, 'enduml' => true, + ); + + /** + * A list with annotations that are not causing exceptions when not resolved to an annotation class. + * + * The names are case sensitive. + * + * @var array + */ + private static $globalIgnoredNamespaces = array(); + + /** + * Add a new annotation to the globally ignored annotation names with regard to exception handling. + * + * @param string $name + */ + static public function addGlobalIgnoredName($name) + { + self::$globalIgnoredNames[$name] = true; + } + + /** + * Add a new annotation to the globally ignored annotation namespaces with regard to exception handling. + * + * @param string $namespace + */ + static public function addGlobalIgnoredNamespace($namespace) + { + self::$globalIgnoredNamespaces[$namespace] = true; + } + + /** + * Annotations parser. + * + * @var \Doctrine\Common\Annotations\DocParser + */ + private $parser; + + /** + * Annotations parser used to collect parsing metadata. + * + * @var \Doctrine\Common\Annotations\DocParser + */ + private $preParser; + + /** + * PHP parser used to collect imports. + * + * @var \Doctrine\Common\Annotations\PhpParser + */ + private $phpParser; + + /** + * In-memory cache mechanism to store imported annotations per class. + * + * @var array + */ + private $imports = array(); + + /** + * In-memory cache mechanism to store ignored annotations per class. + * + * @var array + */ + private $ignoredAnnotationNames = array(); + + /** + * Constructor. + * + * Initializes a new AnnotationReader. + * + * @param DocParser $parser + */ + public function __construct(DocParser $parser = null) + { + if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === "0" || ini_get('opcache.save_comments') === "0")) { + throw AnnotationException::optimizerPlusSaveComments(); + } + + if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') == 0) { + throw AnnotationException::optimizerPlusSaveComments(); + } + + if (PHP_VERSION_ID < 70000) { + if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.load_comments') === "0" || ini_get('opcache.load_comments') === "0")) { + throw AnnotationException::optimizerPlusLoadComments(); + } + + if (extension_loaded('Zend OPcache') && ini_get('opcache.load_comments') == 0) { + throw AnnotationException::optimizerPlusLoadComments(); + } + } + + AnnotationRegistry::registerFile(__DIR__ . '/Annotation/IgnoreAnnotation.php'); + + $this->parser = $parser ?: new DocParser(); + + $this->preParser = new DocParser; + + $this->preParser->setImports(self::$globalImports); + $this->preParser->setIgnoreNotImportedAnnotations(true); + + $this->phpParser = new PhpParser; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + $this->parser->setTarget(Target::TARGET_CLASS); + $this->parser->setImports($this->getClassImports($class)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); + $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); + + return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + $annotations = $this->getClassAnnotations($class); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $context = 'property ' . $class->getName() . "::\$" . $property->getName(); + + $this->parser->setTarget(Target::TARGET_PROPERTY); + $this->parser->setImports($this->getPropertyImports($property)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); + $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); + + return $this->parser->parse($property->getDocComment(), $context); + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) + { + $annotations = $this->getPropertyAnnotations($property); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $context = 'method ' . $class->getName() . '::' . $method->getName() . '()'; + + $this->parser->setTarget(Target::TARGET_METHOD); + $this->parser->setImports($this->getMethodImports($method)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); + $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); + + return $this->parser->parse($method->getDocComment(), $context); + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName) + { + $annotations = $this->getMethodAnnotations($method); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Returns the ignored annotations for the given class. + * + * @param \ReflectionClass $class + * + * @return array + */ + private function getIgnoredAnnotationNames(ReflectionClass $class) + { + $name = $class->getName(); + if (isset($this->ignoredAnnotationNames[$name])) { + return $this->ignoredAnnotationNames[$name]; + } + + $this->collectParsingMetadata($class); + + return $this->ignoredAnnotationNames[$name]; + } + + /** + * Retrieves imports. + * + * @param \ReflectionClass $class + * + * @return array + */ + private function getClassImports(ReflectionClass $class) + { + $name = $class->getName(); + if (isset($this->imports[$name])) { + return $this->imports[$name]; + } + + $this->collectParsingMetadata($class); + + return $this->imports[$name]; + } + + /** + * Retrieves imports for methods. + * + * @param \ReflectionMethod $method + * + * @return array + */ + private function getMethodImports(ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $classImports = $this->getClassImports($class); + if (!method_exists($class, 'getTraits')) { + return $classImports; + } + + $traitImports = array(); + + foreach ($class->getTraits() as $trait) { + if ($trait->hasMethod($method->getName()) + && $trait->getFileName() === $method->getFileName() + ) { + $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait)); + } + } + + return array_merge($classImports, $traitImports); + } + + /** + * Retrieves imports for properties. + * + * @param \ReflectionProperty $property + * + * @return array + */ + private function getPropertyImports(ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $classImports = $this->getClassImports($class); + if (!method_exists($class, 'getTraits')) { + return $classImports; + } + + $traitImports = array(); + + foreach ($class->getTraits() as $trait) { + if ($trait->hasProperty($property->getName())) { + $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait)); + } + } + + return array_merge($classImports, $traitImports); + } + + /** + * Collects parsing metadata for a given class. + * + * @param \ReflectionClass $class + */ + private function collectParsingMetadata(ReflectionClass $class) + { + $ignoredAnnotationNames = self::$globalIgnoredNames; + $annotations = $this->preParser->parse($class->getDocComment(), 'class ' . $class->name); + + foreach ($annotations as $annotation) { + if ($annotation instanceof IgnoreAnnotation) { + foreach ($annotation->names AS $annot) { + $ignoredAnnotationNames[$annot] = true; + } + } + } + + $name = $class->getName(); + + $this->imports[$name] = array_merge( + self::$globalImports, + $this->phpParser->parseClass($class), + array('__NAMESPACE__' => $class->getNamespaceName()) + ); + + $this->ignoredAnnotationNames[$name] = $ignoredAnnotationNames; + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php new file mode 100644 index 00000000..13ceb634 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php @@ -0,0 +1,151 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * AnnotationRegistry. + */ +final class AnnotationRegistry +{ + /** + * A map of namespaces to use for autoloading purposes based on a PSR-0 convention. + * + * Contains the namespace as key and an array of directories as value. If the value is NULL + * the include path is used for checking for the corresponding file. + * + * This autoloading mechanism does not utilize the PHP autoloading but implements autoloading on its own. + * + * @var array + */ + static private $autoloadNamespaces = array(); + + /** + * A map of autoloader callables. + * + * @var array + */ + static private $loaders = array(); + + /** + * @return void + */ + static public function reset() + { + self::$autoloadNamespaces = array(); + self::$loaders = array(); + } + + /** + * Registers file. + * + * @param string $file + * + * @return void + */ + static public function registerFile($file) + { + require_once $file; + } + + /** + * Adds a namespace with one or many directories to look for files or null for the include path. + * + * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. + * + * @param string $namespace + * @param string|array|null $dirs + * + * @return void + */ + static public function registerAutoloadNamespace($namespace, $dirs = null) + { + self::$autoloadNamespaces[$namespace] = $dirs; + } + + /** + * Registers multiple namespaces. + * + * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. + * + * @param array $namespaces + * + * @return void + */ + static public function registerAutoloadNamespaces(array $namespaces) + { + self::$autoloadNamespaces = array_merge(self::$autoloadNamespaces, $namespaces); + } + + /** + * Registers an autoloading callable for annotations, much like spl_autoload_register(). + * + * NOTE: These class loaders HAVE to be silent when a class was not found! + * IMPORTANT: Loaders have to return true if they loaded a class that could contain the searched annotation class. + * + * @param callable $callable + * + * @return void + * + * @throws \InvalidArgumentException + */ + static public function registerLoader($callable) + { + if (!is_callable($callable)) { + throw new \InvalidArgumentException("A callable is expected in AnnotationRegistry::registerLoader()."); + } + self::$loaders[] = $callable; + } + + /** + * Autoloads an annotation class silently. + * + * @param string $class + * + * @return boolean + */ + static public function loadAnnotationClass($class) + { + foreach (self::$autoloadNamespaces AS $namespace => $dirs) { + if (strpos($class, $namespace) === 0) { + $file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php"; + if ($dirs === null) { + if ($path = stream_resolve_include_path($file)) { + require $path; + return true; + } + } else { + foreach((array)$dirs AS $dir) { + if (is_file($dir . DIRECTORY_SEPARATOR . $file)) { + require $dir . DIRECTORY_SEPARATOR . $file; + return true; + } + } + } + } + } + + foreach (self::$loaders AS $loader) { + if (call_user_func($loader, $class) === true) { + return true; + } + } + return false; + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php new file mode 100644 index 00000000..751c1b1b --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php @@ -0,0 +1,262 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use Doctrine\Common\Cache\Cache; +use ReflectionClass; + +/** + * A cache aware annotation reader. + * + * @author Johannes M. Schmitt + * @author Benjamin Eberlei + */ +final class CachedReader implements Reader +{ + /** + * @var Reader + */ + private $delegate; + + /** + * @var Cache + */ + private $cache; + + /** + * @var boolean + */ + private $debug; + + /** + * @var array + */ + private $loadedAnnotations = array(); + + /** + * Constructor. + * + * @param Reader $reader + * @param Cache $cache + * @param bool $debug + */ + public function __construct(Reader $reader, Cache $cache, $debug = false) + { + $this->delegate = $reader; + $this->cache = $cache; + $this->debug = (boolean) $debug; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(ReflectionClass $class) + { + $cacheKey = $class->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) { + $annots = $this->delegate->getClassAnnotations($class); + $this->saveToCache($cacheKey, $annots); + } + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + foreach ($this->getClassAnnotations($class) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(\ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $cacheKey = $class->getName().'$'.$property->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) { + $annots = $this->delegate->getPropertyAnnotations($property); + $this->saveToCache($cacheKey, $annots); + } + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName) + { + foreach ($this->getPropertyAnnotations($property) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(\ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $cacheKey = $class->getName().'#'.$method->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) { + $annots = $this->delegate->getMethodAnnotations($method); + $this->saveToCache($cacheKey, $annots); + } + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(\ReflectionMethod $method, $annotationName) + { + foreach ($this->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * Clears loaded annotations. + * + * @return void + */ + public function clearLoadedAnnotations() + { + $this->loadedAnnotations = array(); + } + + /** + * Fetches a value from the cache. + * + * @param string $cacheKey The cache key. + * @param ReflectionClass $class The related class. + * + * @return mixed The cached value or false when the value is not in cache. + */ + private function fetchFromCache($cacheKey, ReflectionClass $class) + { + if (($data = $this->cache->fetch($cacheKey)) !== false) { + if (!$this->debug || $this->isCacheFresh($cacheKey, $class)) { + return $data; + } + } + + return false; + } + + /** + * Saves a value to the cache. + * + * @param string $cacheKey The cache key. + * @param mixed $value The value. + * + * @return void + */ + private function saveToCache($cacheKey, $value) + { + $this->cache->save($cacheKey, $value); + if ($this->debug) { + $this->cache->save('[C]'.$cacheKey, time()); + } + } + + /** + * Checks if the cache is fresh. + * + * @param string $cacheKey + * @param ReflectionClass $class + * + * @return boolean + */ + private function isCacheFresh($cacheKey, ReflectionClass $class) + { + if (null === $lastModification = $this->getLastModification($class)) { + return true; + } + + return $this->cache->fetch('[C]'.$cacheKey) >= $lastModification; + } + + /** + * Returns the time the class was last modified, testing traits and parents + * + * @param ReflectionClass $class + * @return int + */ + private function getLastModification(ReflectionClass $class) + { + $filename = $class->getFileName(); + $parent = $class->getParentClass(); + + return max(array_merge( + [$filename ? filemtime($filename) : 0], + array_map([$this, 'getTraitLastModificationTime'], $class->getTraits()), + array_map([$this, 'getLastModification'], $class->getInterfaces()), + $parent ? [$this->getLastModification($parent)] : [] + )); + } + + /** + * @param ReflectionClass $reflectionTrait + * @return int + */ + private function getTraitLastModificationTime(ReflectionClass $reflectionTrait) + { + $fileName = $reflectionTrait->getFileName(); + + return max(array_merge( + [$fileName ? filemtime($fileName) : 0], + array_map([$this, 'getTraitLastModificationTime'], $reflectionTrait->getTraits()) + )); + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php new file mode 100644 index 00000000..d864540e --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php @@ -0,0 +1,134 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use Doctrine\Common\Lexer\AbstractLexer; + +/** + * Simple lexer for docblock annotations. + * + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Johannes M. Schmitt + */ +final class DocLexer extends AbstractLexer +{ + const T_NONE = 1; + const T_INTEGER = 2; + const T_STRING = 3; + const T_FLOAT = 4; + + // All tokens that are also identifiers should be >= 100 + const T_IDENTIFIER = 100; + const T_AT = 101; + const T_CLOSE_CURLY_BRACES = 102; + const T_CLOSE_PARENTHESIS = 103; + const T_COMMA = 104; + const T_EQUALS = 105; + const T_FALSE = 106; + const T_NAMESPACE_SEPARATOR = 107; + const T_OPEN_CURLY_BRACES = 108; + const T_OPEN_PARENTHESIS = 109; + const T_TRUE = 110; + const T_NULL = 111; + const T_COLON = 112; + + /** + * @var array + */ + protected $noCase = array( + '@' => self::T_AT, + ',' => self::T_COMMA, + '(' => self::T_OPEN_PARENTHESIS, + ')' => self::T_CLOSE_PARENTHESIS, + '{' => self::T_OPEN_CURLY_BRACES, + '}' => self::T_CLOSE_CURLY_BRACES, + '=' => self::T_EQUALS, + ':' => self::T_COLON, + '\\' => self::T_NAMESPACE_SEPARATOR + ); + + /** + * @var array + */ + protected $withCase = array( + 'true' => self::T_TRUE, + 'false' => self::T_FALSE, + 'null' => self::T_NULL + ); + + /** + * {@inheritdoc} + */ + protected function getCatchablePatterns() + { + return array( + '[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*', + '(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?', + '"(?:""|[^"])*+"', + ); + } + + /** + * {@inheritdoc} + */ + protected function getNonCatchablePatterns() + { + return array('\s+', '\*+', '(.)'); + } + + /** + * {@inheritdoc} + */ + protected function getType(&$value) + { + $type = self::T_NONE; + + if ($value[0] === '"') { + $value = str_replace('""', '"', substr($value, 1, strlen($value) - 2)); + + return self::T_STRING; + } + + if (isset($this->noCase[$value])) { + return $this->noCase[$value]; + } + + if ($value[0] === '_' || $value[0] === '\\' || ctype_alpha($value[0])) { + return self::T_IDENTIFIER; + } + + $lowerValue = strtolower($value); + + if (isset($this->withCase[$lowerValue])) { + return $this->withCase[$lowerValue]; + } + + // Checking numeric value + if (is_numeric($value)) { + return (strpos($value, '.') !== false || stripos($value, 'e') !== false) + ? self::T_FLOAT : self::T_INTEGER; + } + + return $type; + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php new file mode 100644 index 00000000..eb7a457f --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php @@ -0,0 +1,1190 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use Doctrine\Common\Annotations\Annotation\Attribute; +use ReflectionClass; +use Doctrine\Common\Annotations\Annotation\Enum; +use Doctrine\Common\Annotations\Annotation\Target; +use Doctrine\Common\Annotations\Annotation\Attributes; + +/** + * A parser for docblock annotations. + * + * It is strongly discouraged to change the default annotation parsing process. + * + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Johannes M. Schmitt + * @author Fabio B. Silva + */ +final class DocParser +{ + /** + * An array of all valid tokens for a class name. + * + * @var array + */ + private static $classIdentifiers = array( + DocLexer::T_IDENTIFIER, + DocLexer::T_TRUE, + DocLexer::T_FALSE, + DocLexer::T_NULL + ); + + /** + * The lexer. + * + * @var \Doctrine\Common\Annotations\DocLexer + */ + private $lexer; + + /** + * Current target context. + * + * @var integer + */ + private $target; + + /** + * Doc parser used to collect annotation target. + * + * @var \Doctrine\Common\Annotations\DocParser + */ + private static $metadataParser; + + /** + * Flag to control if the current annotation is nested or not. + * + * @var boolean + */ + private $isNestedAnnotation = false; + + /** + * Hashmap containing all use-statements that are to be used when parsing + * the given doc block. + * + * @var array + */ + private $imports = array(); + + /** + * This hashmap is used internally to cache results of class_exists() + * look-ups. + * + * @var array + */ + private $classExists = array(); + + /** + * Whether annotations that have not been imported should be ignored. + * + * @var boolean + */ + private $ignoreNotImportedAnnotations = false; + + /** + * An array of default namespaces if operating in simple mode. + * + * @var string[] + */ + private $namespaces = array(); + + /** + * A list with annotations that are not causing exceptions when not resolved to an annotation class. + * + * The names must be the raw names as used in the class, not the fully qualified + * class names. + * + * @var bool[] indexed by annotation name + */ + private $ignoredAnnotationNames = array(); + + /** + * A list with annotations in namespaced format + * that are not causing exceptions when not resolved to an annotation class. + * + * @var bool[] indexed by namespace name + */ + private $ignoredAnnotationNamespaces = array(); + + /** + * @var string + */ + private $context = ''; + + /** + * Hash-map for caching annotation metadata. + * + * @var array + */ + private static $annotationMetadata = array( + 'Doctrine\Common\Annotations\Annotation\Target' => array( + 'is_annotation' => true, + 'has_constructor' => true, + 'properties' => array(), + 'targets_literal' => 'ANNOTATION_CLASS', + 'targets' => Target::TARGET_CLASS, + 'default_property' => 'value', + 'attribute_types' => array( + 'value' => array( + 'required' => false, + 'type' =>'array', + 'array_type'=>'string', + 'value' =>'array' + ) + ), + ), + 'Doctrine\Common\Annotations\Annotation\Attribute' => array( + 'is_annotation' => true, + 'has_constructor' => false, + 'targets_literal' => 'ANNOTATION_ANNOTATION', + 'targets' => Target::TARGET_ANNOTATION, + 'default_property' => 'name', + 'properties' => array( + 'name' => 'name', + 'type' => 'type', + 'required' => 'required' + ), + 'attribute_types' => array( + 'value' => array( + 'required' => true, + 'type' =>'string', + 'value' =>'string' + ), + 'type' => array( + 'required' =>true, + 'type' =>'string', + 'value' =>'string' + ), + 'required' => array( + 'required' =>false, + 'type' =>'boolean', + 'value' =>'boolean' + ) + ), + ), + 'Doctrine\Common\Annotations\Annotation\Attributes' => array( + 'is_annotation' => true, + 'has_constructor' => false, + 'targets_literal' => 'ANNOTATION_CLASS', + 'targets' => Target::TARGET_CLASS, + 'default_property' => 'value', + 'properties' => array( + 'value' => 'value' + ), + 'attribute_types' => array( + 'value' => array( + 'type' =>'array', + 'required' =>true, + 'array_type'=>'Doctrine\Common\Annotations\Annotation\Attribute', + 'value' =>'array' + ) + ), + ), + 'Doctrine\Common\Annotations\Annotation\Enum' => array( + 'is_annotation' => true, + 'has_constructor' => true, + 'targets_literal' => 'ANNOTATION_PROPERTY', + 'targets' => Target::TARGET_PROPERTY, + 'default_property' => 'value', + 'properties' => array( + 'value' => 'value' + ), + 'attribute_types' => array( + 'value' => array( + 'type' => 'array', + 'required' => true, + ), + 'literal' => array( + 'type' => 'array', + 'required' => false, + ), + ), + ), + ); + + /** + * Hash-map for handle types declaration. + * + * @var array + */ + private static $typeMap = array( + 'float' => 'double', + 'bool' => 'boolean', + // allow uppercase Boolean in honor of George Boole + 'Boolean' => 'boolean', + 'int' => 'integer', + ); + + /** + * Constructs a new DocParser. + */ + public function __construct() + { + $this->lexer = new DocLexer; + } + + /** + * Sets the annotation names that are ignored during the parsing process. + * + * The names are supposed to be the raw names as used in the class, not the + * fully qualified class names. + * + * @param bool[] $names indexed by annotation name + * + * @return void + */ + public function setIgnoredAnnotationNames(array $names) + { + $this->ignoredAnnotationNames = $names; + } + + /** + * Sets the annotation namespaces that are ignored during the parsing process. + * + * @param bool[] $ignoredAnnotationNamespaces indexed by annotation namespace name + * + * @return void + */ + public function setIgnoredAnnotationNamespaces($ignoredAnnotationNamespaces) + { + $this->ignoredAnnotationNamespaces = $ignoredAnnotationNamespaces; + } + + /** + * Sets ignore on not-imported annotations. + * + * @param boolean $bool + * + * @return void + */ + public function setIgnoreNotImportedAnnotations($bool) + { + $this->ignoreNotImportedAnnotations = (boolean) $bool; + } + + /** + * Sets the default namespaces. + * + * @param string $namespace + * + * @return void + * + * @throws \RuntimeException + */ + public function addNamespace($namespace) + { + if ($this->imports) { + throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); + } + + $this->namespaces[] = $namespace; + } + + /** + * Sets the imports. + * + * @param array $imports + * + * @return void + * + * @throws \RuntimeException + */ + public function setImports(array $imports) + { + if ($this->namespaces) { + throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); + } + + $this->imports = $imports; + } + + /** + * Sets current target context as bitmask. + * + * @param integer $target + * + * @return void + */ + public function setTarget($target) + { + $this->target = $target; + } + + /** + * Parses the given docblock string for annotations. + * + * @param string $input The docblock string to parse. + * @param string $context The parsing context. + * + * @return array Array of annotations. If no annotations are found, an empty array is returned. + */ + public function parse($input, $context = '') + { + $pos = $this->findInitialTokenPosition($input); + if ($pos === null) { + return array(); + } + + $this->context = $context; + + $this->lexer->setInput(trim(substr($input, $pos), '* /')); + $this->lexer->moveNext(); + + return $this->Annotations(); + } + + /** + * Finds the first valid annotation + * + * @param string $input The docblock string to parse + * + * @return int|null + */ + private function findInitialTokenPosition($input) + { + $pos = 0; + + // search for first valid annotation + while (($pos = strpos($input, '@', $pos)) !== false) { + $preceding = substr($input, $pos - 1, 1); + + // if the @ is preceded by a space, a tab or * it is valid + if ($pos === 0 || $preceding === ' ' || $preceding === '*' || $preceding === "\t") { + return $pos; + } + + $pos++; + } + + return null; + } + + /** + * Attempts to match the given token with the current lookahead token. + * If they match, updates the lookahead token; otherwise raises a syntax error. + * + * @param integer $token Type of token. + * + * @return boolean True if tokens match; false otherwise. + */ + private function match($token) + { + if ( ! $this->lexer->isNextToken($token) ) { + $this->syntaxError($this->lexer->getLiteral($token)); + } + + return $this->lexer->moveNext(); + } + + /** + * Attempts to match the current lookahead token with any of the given tokens. + * + * If any of them matches, this method updates the lookahead token; otherwise + * a syntax error is raised. + * + * @param array $tokens + * + * @return boolean + */ + private function matchAny(array $tokens) + { + if ( ! $this->lexer->isNextTokenAny($tokens)) { + $this->syntaxError(implode(' or ', array_map(array($this->lexer, 'getLiteral'), $tokens))); + } + + return $this->lexer->moveNext(); + } + + /** + * Generates a new syntax error. + * + * @param string $expected Expected string. + * @param array|null $token Optional token. + * + * @return void + * + * @throws AnnotationException + */ + private function syntaxError($expected, $token = null) + { + if ($token === null) { + $token = $this->lexer->lookahead; + } + + $message = sprintf('Expected %s, got ', $expected); + $message .= ($this->lexer->lookahead === null) + ? 'end of string' + : sprintf("'%s' at position %s", $token['value'], $token['position']); + + if (strlen($this->context)) { + $message .= ' in ' . $this->context; + } + + $message .= '.'; + + throw AnnotationException::syntaxError($message); + } + + /** + * Attempts to check if a class exists or not. This never goes through the PHP autoloading mechanism + * but uses the {@link AnnotationRegistry} to load classes. + * + * @param string $fqcn + * + * @return boolean + */ + private function classExists($fqcn) + { + if (isset($this->classExists[$fqcn])) { + return $this->classExists[$fqcn]; + } + + // first check if the class already exists, maybe loaded through another AnnotationReader + if (class_exists($fqcn, false)) { + return $this->classExists[$fqcn] = true; + } + + // final check, does this class exist? + return $this->classExists[$fqcn] = AnnotationRegistry::loadAnnotationClass($fqcn); + } + + /** + * Collects parsing metadata for a given annotation class + * + * @param string $name The annotation name + * + * @return void + */ + private function collectAnnotationMetadata($name) + { + if (self::$metadataParser === null) { + self::$metadataParser = new self(); + + self::$metadataParser->setIgnoreNotImportedAnnotations(true); + self::$metadataParser->setIgnoredAnnotationNames($this->ignoredAnnotationNames); + self::$metadataParser->setImports(array( + 'enum' => 'Doctrine\Common\Annotations\Annotation\Enum', + 'target' => 'Doctrine\Common\Annotations\Annotation\Target', + 'attribute' => 'Doctrine\Common\Annotations\Annotation\Attribute', + 'attributes' => 'Doctrine\Common\Annotations\Annotation\Attributes' + )); + + AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Enum.php'); + AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Target.php'); + AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Attribute.php'); + AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Attributes.php'); + } + + $class = new \ReflectionClass($name); + $docComment = $class->getDocComment(); + + // Sets default values for annotation metadata + $metadata = array( + 'default_property' => null, + 'has_constructor' => (null !== $constructor = $class->getConstructor()) && $constructor->getNumberOfParameters() > 0, + 'properties' => array(), + 'property_types' => array(), + 'attribute_types' => array(), + 'targets_literal' => null, + 'targets' => Target::TARGET_ALL, + 'is_annotation' => false !== strpos($docComment, '@Annotation'), + ); + + // verify that the class is really meant to be an annotation + if ($metadata['is_annotation']) { + self::$metadataParser->setTarget(Target::TARGET_CLASS); + + foreach (self::$metadataParser->parse($docComment, 'class @' . $name) as $annotation) { + if ($annotation instanceof Target) { + $metadata['targets'] = $annotation->targets; + $metadata['targets_literal'] = $annotation->literal; + + continue; + } + + if ($annotation instanceof Attributes) { + foreach ($annotation->value as $attribute) { + $this->collectAttributeTypeMetadata($metadata, $attribute); + } + } + } + + // if not has a constructor will inject values into public properties + if (false === $metadata['has_constructor']) { + // collect all public properties + foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + $metadata['properties'][$property->name] = $property->name; + + if (false === ($propertyComment = $property->getDocComment())) { + continue; + } + + $attribute = new Attribute(); + + $attribute->required = (false !== strpos($propertyComment, '@Required')); + $attribute->name = $property->name; + $attribute->type = (false !== strpos($propertyComment, '@var') && preg_match('/@var\s+([^\s]+)/',$propertyComment, $matches)) + ? $matches[1] + : 'mixed'; + + $this->collectAttributeTypeMetadata($metadata, $attribute); + + // checks if the property has @Enum + if (false !== strpos($propertyComment, '@Enum')) { + $context = 'property ' . $class->name . "::\$" . $property->name; + + self::$metadataParser->setTarget(Target::TARGET_PROPERTY); + + foreach (self::$metadataParser->parse($propertyComment, $context) as $annotation) { + if ( ! $annotation instanceof Enum) { + continue; + } + + $metadata['enum'][$property->name]['value'] = $annotation->value; + $metadata['enum'][$property->name]['literal'] = ( ! empty($annotation->literal)) + ? $annotation->literal + : $annotation->value; + } + } + } + + // choose the first property as default property + $metadata['default_property'] = reset($metadata['properties']); + } + } + + self::$annotationMetadata[$name] = $metadata; + } + + /** + * Collects parsing metadata for a given attribute. + * + * @param array $metadata + * @param Attribute $attribute + * + * @return void + */ + private function collectAttributeTypeMetadata(&$metadata, Attribute $attribute) + { + // handle internal type declaration + $type = isset(self::$typeMap[$attribute->type]) + ? self::$typeMap[$attribute->type] + : $attribute->type; + + // handle the case if the property type is mixed + if ('mixed' === $type) { + return; + } + + // Evaluate type + switch (true) { + // Checks if the property has array + case (false !== $pos = strpos($type, '<')): + $arrayType = substr($type, $pos + 1, -1); + $type = 'array'; + + if (isset(self::$typeMap[$arrayType])) { + $arrayType = self::$typeMap[$arrayType]; + } + + $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; + break; + + // Checks if the property has type[] + case (false !== $pos = strrpos($type, '[')): + $arrayType = substr($type, 0, $pos); + $type = 'array'; + + if (isset(self::$typeMap[$arrayType])) { + $arrayType = self::$typeMap[$arrayType]; + } + + $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; + break; + } + + $metadata['attribute_types'][$attribute->name]['type'] = $type; + $metadata['attribute_types'][$attribute->name]['value'] = $attribute->type; + $metadata['attribute_types'][$attribute->name]['required'] = $attribute->required; + } + + /** + * Annotations ::= Annotation {[ "*" ]* [Annotation]}* + * + * @return array + */ + private function Annotations() + { + $annotations = array(); + + while (null !== $this->lexer->lookahead) { + if (DocLexer::T_AT !== $this->lexer->lookahead['type']) { + $this->lexer->moveNext(); + continue; + } + + // make sure the @ is preceded by non-catchable pattern + if (null !== $this->lexer->token && $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value'])) { + $this->lexer->moveNext(); + continue; + } + + // make sure the @ is followed by either a namespace separator, or + // an identifier token + if ((null === $peek = $this->lexer->glimpse()) + || (DocLexer::T_NAMESPACE_SEPARATOR !== $peek['type'] && !in_array($peek['type'], self::$classIdentifiers, true)) + || $peek['position'] !== $this->lexer->lookahead['position'] + 1) { + $this->lexer->moveNext(); + continue; + } + + $this->isNestedAnnotation = false; + if (false !== $annot = $this->Annotation()) { + $annotations[] = $annot; + } + } + + return $annotations; + } + + /** + * Annotation ::= "@" AnnotationName MethodCall + * AnnotationName ::= QualifiedName | SimpleName + * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName + * NameSpacePart ::= identifier | null | false | true + * SimpleName ::= identifier | null | false | true + * + * @return mixed False if it is not a valid annotation. + * + * @throws AnnotationException + */ + private function Annotation() + { + $this->match(DocLexer::T_AT); + + // check if we have an annotation + $name = $this->Identifier(); + + // only process names which are not fully qualified, yet + // fully qualified names must start with a \ + $originalName = $name; + + if ('\\' !== $name[0]) { + $pos = strpos($name, '\\'); + $alias = (false === $pos)? $name : substr($name, 0, $pos); + $found = false; + $loweredAlias = strtolower($alias); + + if ($this->namespaces) { + foreach ($this->namespaces as $namespace) { + if ($this->classExists($namespace.'\\'.$name)) { + $name = $namespace.'\\'.$name; + $found = true; + break; + } + } + } elseif (isset($this->imports[$loweredAlias])) { + $found = true; + $name = (false !== $pos) + ? $this->imports[$loweredAlias] . substr($name, $pos) + : $this->imports[$loweredAlias]; + } elseif ( ! isset($this->ignoredAnnotationNames[$name]) + && isset($this->imports['__NAMESPACE__']) + && $this->classExists($this->imports['__NAMESPACE__'] . '\\' . $name) + ) { + $name = $this->imports['__NAMESPACE__'].'\\'.$name; + $found = true; + } elseif (! isset($this->ignoredAnnotationNames[$name]) && $this->classExists($name)) { + $found = true; + } + + if ( ! $found) { + if ($this->isIgnoredAnnotation($name)) { + return false; + } + + throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s was never imported. Did you maybe forget to add a "use" statement for this annotation?', $name, $this->context)); + } + } + + $name = ltrim($name,'\\'); + + if ( ! $this->classExists($name)) { + throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s does not exist, or could not be auto-loaded.', $name, $this->context)); + } + + // at this point, $name contains the fully qualified class name of the + // annotation, and it is also guaranteed that this class exists, and + // that it is loaded + + + // collects the metadata annotation only if there is not yet + if ( ! isset(self::$annotationMetadata[$name])) { + $this->collectAnnotationMetadata($name); + } + + // verify that the class is really meant to be an annotation and not just any ordinary class + if (self::$annotationMetadata[$name]['is_annotation'] === false) { + if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$originalName])) { + return false; + } + + throw AnnotationException::semanticalError(sprintf('The class "%s" is not annotated with @Annotation. Are you sure this class can be used as annotation? If so, then you need to add @Annotation to the _class_ doc comment of "%s". If it is indeed no annotation, then you need to add @IgnoreAnnotation("%s") to the _class_ doc comment of %s.', $name, $name, $originalName, $this->context)); + } + + //if target is nested annotation + $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target; + + // Next will be nested + $this->isNestedAnnotation = true; + + //if annotation does not support current target + if (0 === (self::$annotationMetadata[$name]['targets'] & $target) && $target) { + throw AnnotationException::semanticalError( + sprintf('Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s.', + $originalName, $this->context, self::$annotationMetadata[$name]['targets_literal']) + ); + } + + $values = $this->MethodCall(); + + if (isset(self::$annotationMetadata[$name]['enum'])) { + // checks all declared attributes + foreach (self::$annotationMetadata[$name]['enum'] as $property => $enum) { + // checks if the attribute is a valid enumerator + if (isset($values[$property]) && ! in_array($values[$property], $enum['value'])) { + throw AnnotationException::enumeratorError($property, $name, $this->context, $enum['literal'], $values[$property]); + } + } + } + + // checks all declared attributes + foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) { + if ($property === self::$annotationMetadata[$name]['default_property'] + && !isset($values[$property]) && isset($values['value'])) { + $property = 'value'; + } + + // handle a not given attribute or null value + if (!isset($values[$property])) { + if ($type['required']) { + throw AnnotationException::requiredError($property, $originalName, $this->context, 'a(n) '.$type['value']); + } + + continue; + } + + if ($type['type'] === 'array') { + // handle the case of a single value + if ( ! is_array($values[$property])) { + $values[$property] = array($values[$property]); + } + + // checks if the attribute has array type declaration, such as "array" + if (isset($type['array_type'])) { + foreach ($values[$property] as $item) { + if (gettype($item) !== $type['array_type'] && !$item instanceof $type['array_type']) { + throw AnnotationException::attributeTypeError($property, $originalName, $this->context, 'either a(n) '.$type['array_type'].', or an array of '.$type['array_type'].'s', $item); + } + } + } + } elseif (gettype($values[$property]) !== $type['type'] && !$values[$property] instanceof $type['type']) { + throw AnnotationException::attributeTypeError($property, $originalName, $this->context, 'a(n) '.$type['value'], $values[$property]); + } + } + + // check if the annotation expects values via the constructor, + // or directly injected into public properties + if (self::$annotationMetadata[$name]['has_constructor'] === true) { + return new $name($values); + } + + $instance = new $name(); + + foreach ($values as $property => $value) { + if (!isset(self::$annotationMetadata[$name]['properties'][$property])) { + if ('value' !== $property) { + throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not have a property named "%s". Available properties: %s', $originalName, $this->context, $property, implode(', ', self::$annotationMetadata[$name]['properties']))); + } + + // handle the case if the property has no annotations + if ( ! $property = self::$annotationMetadata[$name]['default_property']) { + throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not accept any values, but got %s.', $originalName, $this->context, json_encode($values))); + } + } + + $instance->{$property} = $value; + } + + return $instance; + } + + /** + * MethodCall ::= ["(" [Values] ")"] + * + * @return array + */ + private function MethodCall() + { + $values = array(); + + if ( ! $this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) { + return $values; + } + + $this->match(DocLexer::T_OPEN_PARENTHESIS); + + if ( ! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { + $values = $this->Values(); + } + + $this->match(DocLexer::T_CLOSE_PARENTHESIS); + + return $values; + } + + /** + * Values ::= Array | Value {"," Value}* [","] + * + * @return array + */ + private function Values() + { + $values = array($this->Value()); + + while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { + $this->match(DocLexer::T_COMMA); + + if ($this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { + break; + } + + $token = $this->lexer->lookahead; + $value = $this->Value(); + + if ( ! is_object($value) && ! is_array($value)) { + $this->syntaxError('Value', $token); + } + + $values[] = $value; + } + + foreach ($values as $k => $value) { + if (is_object($value) && $value instanceof \stdClass) { + $values[$value->name] = $value->value; + } else if ( ! isset($values['value'])){ + $values['value'] = $value; + } else { + if ( ! is_array($values['value'])) { + $values['value'] = array($values['value']); + } + + $values['value'][] = $value; + } + + unset($values[$k]); + } + + return $values; + } + + /** + * Constant ::= integer | string | float | boolean + * + * @return mixed + * + * @throws AnnotationException + */ + private function Constant() + { + $identifier = $this->Identifier(); + + if ( ! defined($identifier) && false !== strpos($identifier, '::') && '\\' !== $identifier[0]) { + list($className, $const) = explode('::', $identifier); + + $pos = strpos($className, '\\'); + $alias = (false === $pos) ? $className : substr($className, 0, $pos); + $found = false; + $loweredAlias = strtolower($alias); + + switch (true) { + case !empty ($this->namespaces): + foreach ($this->namespaces as $ns) { + if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) { + $className = $ns.'\\'.$className; + $found = true; + break; + } + } + break; + + case isset($this->imports[$loweredAlias]): + $found = true; + $className = (false !== $pos) + ? $this->imports[$loweredAlias] . substr($className, $pos) + : $this->imports[$loweredAlias]; + break; + + default: + if(isset($this->imports['__NAMESPACE__'])) { + $ns = $this->imports['__NAMESPACE__']; + + if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) { + $className = $ns.'\\'.$className; + $found = true; + } + } + break; + } + + if ($found) { + $identifier = $className . '::' . $const; + } + } + + // checks if identifier ends with ::class, \strlen('::class') === 7 + $classPos = stripos($identifier, '::class'); + if ($classPos === strlen($identifier) - 7) { + return substr($identifier, 0, $classPos); + } + + if (!defined($identifier)) { + throw AnnotationException::semanticalErrorConstants($identifier, $this->context); + } + + return constant($identifier); + } + + /** + * Identifier ::= string + * + * @return string + */ + private function Identifier() + { + // check if we have an annotation + if ( ! $this->lexer->isNextTokenAny(self::$classIdentifiers)) { + $this->syntaxError('namespace separator or identifier'); + } + + $this->lexer->moveNext(); + + $className = $this->lexer->token['value']; + + while ($this->lexer->lookahead['position'] === ($this->lexer->token['position'] + strlen($this->lexer->token['value'])) + && $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) { + + $this->match(DocLexer::T_NAMESPACE_SEPARATOR); + $this->matchAny(self::$classIdentifiers); + + $className .= '\\' . $this->lexer->token['value']; + } + + return $className; + } + + /** + * Value ::= PlainValue | FieldAssignment + * + * @return mixed + */ + private function Value() + { + $peek = $this->lexer->glimpse(); + + if (DocLexer::T_EQUALS === $peek['type']) { + return $this->FieldAssignment(); + } + + return $this->PlainValue(); + } + + /** + * PlainValue ::= integer | string | float | boolean | Array | Annotation + * + * @return mixed + */ + private function PlainValue() + { + if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) { + return $this->Arrayx(); + } + + if ($this->lexer->isNextToken(DocLexer::T_AT)) { + return $this->Annotation(); + } + + if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { + return $this->Constant(); + } + + switch ($this->lexer->lookahead['type']) { + case DocLexer::T_STRING: + $this->match(DocLexer::T_STRING); + return $this->lexer->token['value']; + + case DocLexer::T_INTEGER: + $this->match(DocLexer::T_INTEGER); + return (int)$this->lexer->token['value']; + + case DocLexer::T_FLOAT: + $this->match(DocLexer::T_FLOAT); + return (float)$this->lexer->token['value']; + + case DocLexer::T_TRUE: + $this->match(DocLexer::T_TRUE); + return true; + + case DocLexer::T_FALSE: + $this->match(DocLexer::T_FALSE); + return false; + + case DocLexer::T_NULL: + $this->match(DocLexer::T_NULL); + return null; + + default: + $this->syntaxError('PlainValue'); + } + } + + /** + * FieldAssignment ::= FieldName "=" PlainValue + * FieldName ::= identifier + * + * @return \stdClass + */ + private function FieldAssignment() + { + $this->match(DocLexer::T_IDENTIFIER); + $fieldName = $this->lexer->token['value']; + + $this->match(DocLexer::T_EQUALS); + + $item = new \stdClass(); + $item->name = $fieldName; + $item->value = $this->PlainValue(); + + return $item; + } + + /** + * Array ::= "{" ArrayEntry {"," ArrayEntry}* [","] "}" + * + * @return array + */ + private function Arrayx() + { + $array = $values = array(); + + $this->match(DocLexer::T_OPEN_CURLY_BRACES); + + // If the array is empty, stop parsing and return. + if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { + $this->match(DocLexer::T_CLOSE_CURLY_BRACES); + + return $array; + } + + $values[] = $this->ArrayEntry(); + + while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { + $this->match(DocLexer::T_COMMA); + + // optional trailing comma + if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { + break; + } + + $values[] = $this->ArrayEntry(); + } + + $this->match(DocLexer::T_CLOSE_CURLY_BRACES); + + foreach ($values as $value) { + list ($key, $val) = $value; + + if ($key !== null) { + $array[$key] = $val; + } else { + $array[] = $val; + } + } + + return $array; + } + + /** + * ArrayEntry ::= Value | KeyValuePair + * KeyValuePair ::= Key ("=" | ":") PlainValue | Constant + * Key ::= string | integer | Constant + * + * @return array + */ + private function ArrayEntry() + { + $peek = $this->lexer->glimpse(); + + if (DocLexer::T_EQUALS === $peek['type'] + || DocLexer::T_COLON === $peek['type']) { + + if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { + $key = $this->Constant(); + } else { + $this->matchAny(array(DocLexer::T_INTEGER, DocLexer::T_STRING)); + $key = $this->lexer->token['value']; + } + + $this->matchAny(array(DocLexer::T_EQUALS, DocLexer::T_COLON)); + + return array($key, $this->PlainValue()); + } + + return array(null, $this->Value()); + } + + /** + * Checks whether the given $name matches any ignored annotation name or namespace + * + * @param string $name + * + * @return bool + */ + private function isIgnoredAnnotation($name) + { + if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) { + return true; + } + + foreach (array_keys($this->ignoredAnnotationNamespaces) as $ignoredAnnotationNamespace) { + $ignoredAnnotationNamespace = rtrim($ignoredAnnotationNamespace, '\\') . '\\'; + + if (0 === stripos(rtrim($name, '\\') . '\\', $ignoredAnnotationNamespace)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php new file mode 100644 index 00000000..fd2fedee --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php @@ -0,0 +1,290 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * File cache reader for annotations. + * + * @author Johannes M. Schmitt + * @author Benjamin Eberlei + * + * @deprecated the FileCacheReader is deprecated and will be removed + * in version 2.0.0 of doctrine/annotations. Please use the + * {@see \Doctrine\Common\Annotations\CachedReader} instead. + */ +class FileCacheReader implements Reader +{ + /** + * @var Reader + */ + private $reader; + + /** + * @var string + */ + private $dir; + + /** + * @var bool + */ + private $debug; + + /** + * @var array + */ + private $loadedAnnotations = array(); + + /** + * @var array + */ + private $classNameHashes = array(); + + /** + * @var int + */ + private $umask; + + /** + * Constructor. + * + * @param Reader $reader + * @param string $cacheDir + * @param boolean $debug + * + * @throws \InvalidArgumentException + */ + public function __construct(Reader $reader, $cacheDir, $debug = false, $umask = 0002) + { + if ( ! is_int($umask)) { + throw new \InvalidArgumentException(sprintf( + 'The parameter umask must be an integer, was: %s', + gettype($umask) + )); + } + + $this->reader = $reader; + $this->umask = $umask; + + if (!is_dir($cacheDir) && !@mkdir($cacheDir, 0777 & (~$this->umask), true)) { + throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $cacheDir)); + } + + $this->dir = rtrim($cacheDir, '\\/'); + $this->debug = $debug; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(\ReflectionClass $class) + { + if ( ! isset($this->classNameHashes[$class->name])) { + $this->classNameHashes[$class->name] = sha1($class->name); + } + $key = $this->classNameHashes[$class->name]; + + if (isset($this->loadedAnnotations[$key])) { + return $this->loadedAnnotations[$key]; + } + + $path = $this->dir.'/'.strtr($key, '\\', '-').'.cache.php'; + if (!is_file($path)) { + $annot = $this->reader->getClassAnnotations($class); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + if ($this->debug + && (false !== $filename = $class->getFileName()) + && filemtime($path) < filemtime($filename)) { + @unlink($path); + + $annot = $this->reader->getClassAnnotations($class); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + return $this->loadedAnnotations[$key] = include $path; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(\ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + if ( ! isset($this->classNameHashes[$class->name])) { + $this->classNameHashes[$class->name] = sha1($class->name); + } + $key = $this->classNameHashes[$class->name].'$'.$property->getName(); + + if (isset($this->loadedAnnotations[$key])) { + return $this->loadedAnnotations[$key]; + } + + $path = $this->dir.'/'.strtr($key, '\\', '-').'.cache.php'; + if (!is_file($path)) { + $annot = $this->reader->getPropertyAnnotations($property); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + if ($this->debug + && (false !== $filename = $class->getFilename()) + && filemtime($path) < filemtime($filename)) { + @unlink($path); + + $annot = $this->reader->getPropertyAnnotations($property); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + return $this->loadedAnnotations[$key] = include $path; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(\ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + if ( ! isset($this->classNameHashes[$class->name])) { + $this->classNameHashes[$class->name] = sha1($class->name); + } + $key = $this->classNameHashes[$class->name].'#'.$method->getName(); + + if (isset($this->loadedAnnotations[$key])) { + return $this->loadedAnnotations[$key]; + } + + $path = $this->dir.'/'.strtr($key, '\\', '-').'.cache.php'; + if (!is_file($path)) { + $annot = $this->reader->getMethodAnnotations($method); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + if ($this->debug + && (false !== $filename = $class->getFilename()) + && filemtime($path) < filemtime($filename)) { + @unlink($path); + + $annot = $this->reader->getMethodAnnotations($method); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + return $this->loadedAnnotations[$key] = include $path; + } + + /** + * Saves the cache file. + * + * @param string $path + * @param mixed $data + * + * @return void + */ + private function saveCacheFile($path, $data) + { + if (!is_writable($this->dir)) { + throw new \InvalidArgumentException(sprintf('The directory "%s" is not writable. Both, the webserver and the console user need access. You can manage access rights for multiple users with "chmod +a". If your system does not support this, check out the acl package.', $this->dir)); + } + + $tempfile = tempnam($this->dir, uniqid('', true)); + + if (false === $tempfile) { + throw new \RuntimeException(sprintf('Unable to create tempfile in directory: %s', $this->dir)); + } + + @chmod($tempfile, 0666 & (~$this->umask)); + + $written = file_put_contents($tempfile, 'umask)); + + if (false === rename($tempfile, $path)) { + @unlink($tempfile); + throw new \RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path)); + } + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(\ReflectionClass $class, $annotationName) + { + $annotations = $this->getClassAnnotations($class); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(\ReflectionMethod $method, $annotationName) + { + $annotations = $this->getMethodAnnotations($method); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName) + { + $annotations = $this->getPropertyAnnotations($property); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Clears loaded annotations. + * + * @return void + */ + public function clearLoadedAnnotations() + { + $this->loadedAnnotations = array(); + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php new file mode 100644 index 00000000..bf7fbdcd --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php @@ -0,0 +1,119 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Allows the reader to be used in-place of Doctrine's reader. + * + * @author Johannes M. Schmitt + */ +class IndexedReader implements Reader +{ + /** + * @var Reader + */ + private $delegate; + + /** + * Constructor. + * + * @param Reader $reader + */ + public function __construct(Reader $reader) + { + $this->delegate = $reader; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(\ReflectionClass $class) + { + $annotations = array(); + foreach ($this->delegate->getClassAnnotations($class) as $annot) { + $annotations[get_class($annot)] = $annot; + } + + return $annotations; + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(\ReflectionClass $class, $annotation) + { + return $this->delegate->getClassAnnotation($class, $annotation); + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(\ReflectionMethod $method) + { + $annotations = array(); + foreach ($this->delegate->getMethodAnnotations($method) as $annot) { + $annotations[get_class($annot)] = $annot; + } + + return $annotations; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(\ReflectionMethod $method, $annotation) + { + return $this->delegate->getMethodAnnotation($method, $annotation); + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(\ReflectionProperty $property) + { + $annotations = array(); + foreach ($this->delegate->getPropertyAnnotations($property) as $annot) { + $annotations[get_class($annot)] = $annot; + } + + return $annotations; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(\ReflectionProperty $property, $annotation) + { + return $this->delegate->getPropertyAnnotation($property, $annotation); + } + + /** + * Proxies all methods to the delegate. + * + * @param string $method + * @param array $args + * + * @return mixed + */ + public function __call($method, $args) + { + return call_user_func_array(array($this->delegate, $method), $args); + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php new file mode 100644 index 00000000..c2d47704 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php @@ -0,0 +1,91 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use SplFileObject; + +/** + * Parses a file for namespaces/use/class declarations. + * + * @author Fabien Potencier + * @author Christian Kaps + */ +final class PhpParser +{ + /** + * Parses a class. + * + * @param \ReflectionClass $class A ReflectionClass object. + * + * @return array A list with use statements in the form (Alias => FQN). + */ + public function parseClass(\ReflectionClass $class) + { + if (method_exists($class, 'getUseStatements')) { + return $class->getUseStatements(); + } + + if (false === $filename = $class->getFileName()) { + return array(); + } + + $content = $this->getFileContent($filename, $class->getStartLine()); + + if (null === $content) { + return array(); + } + + $namespace = preg_quote($class->getNamespaceName()); + $content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content); + $tokenizer = new TokenParser('parseUseStatements($class->getNamespaceName()); + + return $statements; + } + + /** + * Gets the content of the file right up to the given line number. + * + * @param string $filename The name of the file to load. + * @param integer $lineNumber The number of lines to read from file. + * + * @return string|null The content of the file or null if the file does not exist. + */ + private function getFileContent($filename, $lineNumber) + { + if ( ! is_file($filename)) { + return null; + } + + $content = ''; + $lineCnt = 0; + $file = new SplFileObject($filename); + while (!$file->eof()) { + if ($lineCnt++ == $lineNumber) { + break; + } + + $content .= $file->fgets(); + } + + return $content; + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php new file mode 100644 index 00000000..4774f873 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php @@ -0,0 +1,89 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Interface for annotation readers. + * + * @author Johannes M. Schmitt + */ +interface Reader +{ + /** + * Gets the annotations applied to a class. + * + * @param \ReflectionClass $class The ReflectionClass of the class from which + * the class annotations should be read. + * + * @return array An array of Annotations. + */ + function getClassAnnotations(\ReflectionClass $class); + + /** + * Gets a class annotation. + * + * @param \ReflectionClass $class The ReflectionClass of the class from which + * the class annotations should be read. + * @param string $annotationName The name of the annotation. + * + * @return object|null The Annotation or NULL, if the requested annotation does not exist. + */ + function getClassAnnotation(\ReflectionClass $class, $annotationName); + + /** + * Gets the annotations applied to a method. + * + * @param \ReflectionMethod $method The ReflectionMethod of the method from which + * the annotations should be read. + * + * @return array An array of Annotations. + */ + function getMethodAnnotations(\ReflectionMethod $method); + + /** + * Gets a method annotation. + * + * @param \ReflectionMethod $method The ReflectionMethod to read the annotations from. + * @param string $annotationName The name of the annotation. + * + * @return object|null The Annotation or NULL, if the requested annotation does not exist. + */ + function getMethodAnnotation(\ReflectionMethod $method, $annotationName); + + /** + * Gets the annotations applied to a property. + * + * @param \ReflectionProperty $property The ReflectionProperty of the property + * from which the annotations should be read. + * + * @return array An array of Annotations. + */ + function getPropertyAnnotations(\ReflectionProperty $property); + + /** + * Gets a property annotation. + * + * @param \ReflectionProperty $property The ReflectionProperty to read the annotations from. + * @param string $annotationName The name of the annotation. + * + * @return object|null The Annotation or NULL, if the requested annotation does not exist. + */ + function getPropertyAnnotation(\ReflectionProperty $property, $annotationName); +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php new file mode 100644 index 00000000..d4757eea --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php @@ -0,0 +1,127 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Simple Annotation Reader. + * + * This annotation reader is intended to be used in projects where you have + * full-control over all annotations that are available. + * + * @since 2.2 + * @author Johannes M. Schmitt + * @author Fabio B. Silva + */ +class SimpleAnnotationReader implements Reader +{ + /** + * @var DocParser + */ + private $parser; + + /** + * Constructor. + * + * Initializes a new SimpleAnnotationReader. + */ + public function __construct() + { + $this->parser = new DocParser(); + $this->parser->setIgnoreNotImportedAnnotations(true); + } + + /** + * Adds a namespace in which we will look for annotations. + * + * @param string $namespace + * + * @return void + */ + public function addNamespace($namespace) + { + $this->parser->addNamespace($namespace); + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotations(\ReflectionClass $class) + { + return $this->parser->parse($class->getDocComment(), 'class '.$class->getName()); + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotations(\ReflectionMethod $method) + { + return $this->parser->parse($method->getDocComment(), 'method '.$method->getDeclaringClass()->name.'::'.$method->getName().'()'); + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotations(\ReflectionProperty $property) + { + return $this->parser->parse($property->getDocComment(), 'property '.$property->getDeclaringClass()->name.'::$'.$property->getName()); + } + + /** + * {@inheritDoc} + */ + public function getClassAnnotation(\ReflectionClass $class, $annotationName) + { + foreach ($this->getClassAnnotations($class) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getMethodAnnotation(\ReflectionMethod $method, $annotationName) + { + foreach ($this->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName) + { + foreach ($this->getPropertyAnnotations($property) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php new file mode 100644 index 00000000..bf1b7133 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php @@ -0,0 +1,194 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Parses a file for namespaces/use/class declarations. + * + * @author Fabien Potencier + * @author Christian Kaps + */ +class TokenParser +{ + /** + * The token list. + * + * @var array + */ + private $tokens; + + /** + * The number of tokens. + * + * @var int + */ + private $numTokens; + + /** + * The current array pointer. + * + * @var int + */ + private $pointer = 0; + + /** + * @param string $contents + */ + public function __construct($contents) + { + $this->tokens = token_get_all($contents); + + // The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it + // saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored + // doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a + // docblock. If the first thing in the file is a class without a doc block this would cause calls to + // getDocBlock() on said class to return our long lost doc_comment. Argh. + // To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least + // it's harmless to us. + token_get_all("numTokens = count($this->tokens); + } + + /** + * Gets the next non whitespace and non comment token. + * + * @param boolean $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped. + * If FALSE then only whitespace and normal comments are skipped. + * + * @return array|null The token if exists, null otherwise. + */ + public function next($docCommentIsComment = TRUE) + { + for ($i = $this->pointer; $i < $this->numTokens; $i++) { + $this->pointer++; + if ($this->tokens[$i][0] === T_WHITESPACE || + $this->tokens[$i][0] === T_COMMENT || + ($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT)) { + + continue; + } + + return $this->tokens[$i]; + } + + return null; + } + + /** + * Parses a single use statement. + * + * @return array A list with all found class names for a use statement. + */ + public function parseUseStatement() + { + + $groupRoot = ''; + $class = ''; + $alias = ''; + $statements = array(); + $explicitAlias = false; + while (($token = $this->next())) { + $isNameToken = $token[0] === T_STRING || $token[0] === T_NS_SEPARATOR; + if (!$explicitAlias && $isNameToken) { + $class .= $token[1]; + $alias = $token[1]; + } else if ($explicitAlias && $isNameToken) { + $alias .= $token[1]; + } else if ($token[0] === T_AS) { + $explicitAlias = true; + $alias = ''; + } else if ($token === ',') { + $statements[strtolower($alias)] = $groupRoot . $class; + $class = ''; + $alias = ''; + $explicitAlias = false; + } else if ($token === ';') { + $statements[strtolower($alias)] = $groupRoot . $class; + break; + } else if ($token === '{' ) { + $groupRoot = $class; + $class = ''; + } else if ($token === '}' ) { + continue; + } else { + break; + } + } + + return $statements; + } + + /** + * Gets all use statements. + * + * @param string $namespaceName The namespace name of the reflected class. + * + * @return array A list with all found use statements. + */ + public function parseUseStatements($namespaceName) + { + $statements = array(); + while (($token = $this->next())) { + if ($token[0] === T_USE) { + $statements = array_merge($statements, $this->parseUseStatement()); + continue; + } + if ($token[0] !== T_NAMESPACE || $this->parseNamespace() != $namespaceName) { + continue; + } + + // Get fresh array for new namespace. This is to prevent the parser to collect the use statements + // for a previous namespace with the same name. This is the case if a namespace is defined twice + // or if a namespace with the same name is commented out. + $statements = array(); + } + + return $statements; + } + + /** + * Gets the namespace. + * + * @return string The found namespace. + */ + public function parseNamespace() + { + $name = ''; + while (($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) { + $name .= $token[1]; + } + + return $name; + } + + /** + * Gets the class name. + * + * @return string The found class name. + */ + public function parseClass() + { + // Namespaces and class names are tokenized the same: T_STRINGs + // separated by T_NS_SEPARATOR so we can use one function to provide + // both. + return $this->parseNamespace(); + } +} diff --git a/vendor/doctrine/annotations/phpstan.neon b/vendor/doctrine/annotations/phpstan.neon new file mode 100644 index 00000000..d2b79a7f --- /dev/null +++ b/vendor/doctrine/annotations/phpstan.neon @@ -0,0 +1,14 @@ +parameters: + autoload_files: + - %currentWorkingDirectory%/tests/Doctrine/Tests/Common/Annotations/DocParserTest.php + excludes_analyse: + - %currentWorkingDirectory%/tests/*/Fixtures/* + polluteScopeWithLoopInitialAssignments: true + ignoreErrors: + - '#Class Doctrine_Tests_Common_Annotations_Fixtures_ClassNoNamespaceNoComment not found#' + - '#Instantiated class Doctrine_Tests_Common_Annotations_Fixtures_ClassNoNamespaceNoComment not found#' + - '#Property Doctrine\\Tests\\Common\\Annotations\\DummyClassNonAnnotationProblem::\$foo has unknown class#' + - '#Class Doctrine\\Tests\\Common\\Annotations\\True not found#' + - '#Class Doctrine\\Tests\\Common\\Annotations\\False not found#' + - '#Class Doctrine\\Tests\\Common\\Annotations\\Null not found#' + - '#Call to an undefined method ReflectionClass::getUseStatements\(\)#' diff --git a/vendor/doctrine/cache/.coveralls.yml b/vendor/doctrine/cache/.coveralls.yml new file mode 100644 index 00000000..0c082336 --- /dev/null +++ b/vendor/doctrine/cache/.coveralls.yml @@ -0,0 +1,4 @@ +# for php-coveralls +service_name: travis-ci +src_dir: lib +coverage_clover: build/logs/clover.xml diff --git a/vendor/doctrine/cache/.gitignore b/vendor/doctrine/cache/.gitignore new file mode 100644 index 00000000..ca230231 --- /dev/null +++ b/vendor/doctrine/cache/.gitignore @@ -0,0 +1,4 @@ +vendor/ +build/ +phpunit.xml +composer.lock \ No newline at end of file diff --git a/vendor/doctrine/cache/.travis.yml b/vendor/doctrine/cache/.travis.yml new file mode 100644 index 00000000..a16fd9d9 --- /dev/null +++ b/vendor/doctrine/cache/.travis.yml @@ -0,0 +1,42 @@ +language: php + +sudo: false + +cache: + directories: + - vendor + - $HOME/.composer/cache + +php: + - 5.5 + - 5.6 + - 7.0 + - hhvm + +services: + - riak + - mongodb + - memcached + - redis-server + +before_install: + - if [[ $TRAVIS_PHP_VERSION != 'hhvm' ]] ; then pecl channel-update pecl.php.net; fi; + - if [[ $TRAVIS_PHP_VERSION != 'hhvm' && $TRAVIS_PHP_VERSION != '7.0' ]]; then pecl install riak-beta; fi; + - if [[ $TRAVIS_PHP_VERSION =~ 5.[56] ]] ; then echo yes | pecl install apcu-4.0.10; fi; + - if [[ $TRAVIS_PHP_VERSION = 7.* ]] ; then pecl config-set preferred_state beta; echo yes | pecl install apcu; fi; + - if [[ $TRAVIS_PHP_VERSION != 'hhvm' ]]; then phpenv config-add ./tests/travis/php.ini; fi; + +install: + - travis_retry composer install + +script: + - ./vendor/bin/phpunit -c ./tests/travis/phpunit.travis.xml -v + +after_script: + - php vendor/bin/coveralls -v + +matrix: + fast_finish: true + allow_failures: + - php: hhvm + - php: 7.0 diff --git a/vendor/doctrine/cache/LICENSE b/vendor/doctrine/cache/LICENSE new file mode 100644 index 00000000..8c38cc1b --- /dev/null +++ b/vendor/doctrine/cache/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2015 Doctrine Project + +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/vendor/doctrine/cache/README.md b/vendor/doctrine/cache/README.md new file mode 100644 index 00000000..94f80a30 --- /dev/null +++ b/vendor/doctrine/cache/README.md @@ -0,0 +1,14 @@ +# Doctrine Cache + +Master: [![Build Status](https://secure.travis-ci.org/doctrine/cache.png?branch=master)](http://travis-ci.org/doctrine/cache) [![Coverage Status](https://coveralls.io/repos/doctrine/cache/badge.png?branch=master)](https://coveralls.io/r/doctrine/cache?branch=master) + +[![Latest Stable Version](https://poser.pugx.org/doctrine/cache/v/stable.png)](https://packagist.org/packages/doctrine/cache) [![Total Downloads](https://poser.pugx.org/doctrine/cache/downloads.png)](https://packagist.org/packages/doctrine/cache) + +Cache component extracted from the Doctrine Common project. + +## Changelog + +### v1.2 + +* Added support for MongoDB as Cache Provider +* Fix namespace version reset diff --git a/vendor/doctrine/cache/UPGRADE.md b/vendor/doctrine/cache/UPGRADE.md new file mode 100644 index 00000000..e1f8a503 --- /dev/null +++ b/vendor/doctrine/cache/UPGRADE.md @@ -0,0 +1,16 @@ +# Upgrade to 1.4 + +## Minor BC Break: `Doctrine\Common\Cache\FileCache#$extension` is now `private`. + +If you need to override the value of `Doctrine\Common\Cache\FileCache#$extension`, then use the +second parameter of `Doctrine\Common\Cache\FileCache#__construct()` instead of overriding +the property in your own implementation. + +## Minor BC Break: file based caches paths changed + +`Doctrine\Common\Cache\FileCache`, `Doctrine\Common\Cache\PhpFileCache` and +`Doctrine\Common\Cache\FilesystemCache` are using a different cache paths structure. + +If you rely on warmed up caches for deployments, consider that caches generated +with `doctrine/cache` `<1.4` are not compatible with the new directory structure, +and will be ignored. diff --git a/vendor/doctrine/cache/build.properties b/vendor/doctrine/cache/build.properties new file mode 100644 index 00000000..2d98c360 --- /dev/null +++ b/vendor/doctrine/cache/build.properties @@ -0,0 +1,3 @@ +# Version class and file +project.version_class = Doctrine\\Common\\Cache\\Version +project.version_file = lib/Doctrine/Common/Cache/Version.php diff --git a/vendor/doctrine/cache/build.xml b/vendor/doctrine/cache/build.xml new file mode 100644 index 00000000..a7c52e3c --- /dev/null +++ b/vendor/doctrine/cache/build.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/doctrine/cache/composer.json b/vendor/doctrine/cache/composer.json new file mode 100644 index 00000000..7ef1727a --- /dev/null +++ b/vendor/doctrine/cache/composer.json @@ -0,0 +1,37 @@ +{ + "name": "doctrine/cache", + "type": "library", + "description": "Caching library offering an object-oriented API for many cache backends", + "keywords": ["cache", "caching"], + "homepage": "http://www.doctrine-project.org", + "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"} + ], + "require": { + "php": "~5.5|~7.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8|~5.0", + "satooshi/php-coveralls": "~0.6", + "predis/predis": "~1.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "autoload": { + "psr-4": { "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" } + }, + "autoload-dev": { + "psr-4": { "Doctrine\\Tests\\": "tests/Doctrine/Tests" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php new file mode 100644 index 00000000..0dfbd6a1 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php @@ -0,0 +1,118 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * APC cache provider. + * + * @link www.doctrine-project.org + * @deprecated since version 1.6, use ApcuCache instead + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class ApcCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return apc_fetch($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return apc_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return apc_store($id, $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + // apc_delete returns false if the id does not exist + return apc_delete($id) || ! apc_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return apc_clear_cache() && apc_clear_cache('user'); + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + return apc_fetch($keys) ?: []; + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + $result = apc_store($keysAndValues, null, $lifetime); + + return empty($result); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = apc_cache_info('', true); + $sma = apc_sma_info(); + + // @TODO - Temporary fix @see https://github.com/krakjoe/apcu/pull/42 + if (PHP_VERSION_ID >= 50500) { + $info['num_hits'] = isset($info['num_hits']) ? $info['num_hits'] : $info['nhits']; + $info['num_misses'] = isset($info['num_misses']) ? $info['num_misses'] : $info['nmisses']; + $info['start_time'] = isset($info['start_time']) ? $info['start_time'] : $info['stime']; + } + + return array( + Cache::STATS_HITS => $info['num_hits'], + Cache::STATS_MISSES => $info['num_misses'], + Cache::STATS_UPTIME => $info['start_time'], + Cache::STATS_MEMORY_USAGE => $info['mem_size'], + Cache::STATS_MEMORY_AVAILABLE => $sma['avail_mem'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcuCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcuCache.php new file mode 100644 index 00000000..2a91752b --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcuCache.php @@ -0,0 +1,106 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * APCu cache provider. + * + * @link www.doctrine-project.org + * @since 1.6 + * @author Kévin Dunglas + */ +class ApcuCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return apcu_fetch($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return apcu_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return apcu_store($id, $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + // apcu_delete returns false if the id does not exist + return apcu_delete($id) || ! apcu_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return apcu_clear_cache(); + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + return apcu_fetch($keys) ?: []; + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + $result = apcu_store($keysAndValues, null, $lifetime); + + return empty($result); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = apcu_cache_info(true); + $sma = apcu_sma_info(); + + return array( + Cache::STATS_HITS => $info['num_hits'], + Cache::STATS_MISSES => $info['num_misses'], + Cache::STATS_UPTIME => $info['start_time'], + Cache::STATS_MEMORY_USAGE => $info['mem_size'], + Cache::STATS_MEMORY_AVAILABLE => $sma['avail_mem'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php new file mode 100644 index 00000000..6610cc21 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php @@ -0,0 +1,142 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Array cache driver. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class ArrayCache extends CacheProvider +{ + /** + * @var array[] $data each element being a tuple of [$data, $expiration], where the expiration is int|bool + */ + private $data = []; + + /** + * @var int + */ + private $hitsCount = 0; + + /** + * @var int + */ + private $missesCount = 0; + + /** + * @var int + */ + private $upTime; + + /** + * {@inheritdoc} + */ + public function __construct() + { + $this->upTime = time(); + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + if (! $this->doContains($id)) { + $this->missesCount += 1; + + return false; + } + + $this->hitsCount += 1; + + return $this->data[$id][0]; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + if (! isset($this->data[$id])) { + return false; + } + + $expiration = $this->data[$id][1]; + + if ($expiration && $expiration < time()) { + $this->doDelete($id); + + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $this->data[$id] = [$data, $lifeTime ? time() + $lifeTime : false]; + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + unset($this->data[$id]); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $this->data = []; + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + return [ + Cache::STATS_HITS => $this->hitsCount, + Cache::STATS_MISSES => $this->missesCount, + Cache::STATS_UPTIME => $this->upTime, + Cache::STATS_MEMORY_USAGE => null, + Cache::STATS_MEMORY_AVAILABLE => null, + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php new file mode 100644 index 00000000..89fe3230 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php @@ -0,0 +1,116 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Interface for cache drivers. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Fabio B. Silva + * @author Kévin Dunglas + */ +interface Cache +{ + const STATS_HITS = 'hits'; + const STATS_MISSES = 'misses'; + const STATS_UPTIME = 'uptime'; + const STATS_MEMORY_USAGE = 'memory_usage'; + const STATS_MEMORY_AVAILABLE = 'memory_available'; + /** + * Only for backward compatibility (may be removed in next major release) + * + * @deprecated + */ + const STATS_MEMORY_AVAILIABLE = 'memory_available'; + + /** + * Fetches an entry from the cache. + * + * @param string $id The id of the cache entry to fetch. + * + * @return mixed The cached data or FALSE, if no cache entry exists for the given id. + */ + public function fetch($id); + + /** + * Tests if an entry exists in the cache. + * + * @param string $id The cache id of the entry to check for. + * + * @return bool TRUE if a cache entry exists for the given cache id, FALSE otherwise. + */ + public function contains($id); + + /** + * Puts data into the cache. + * + * If a cache entry with the given id already exists, its data will be replaced. + * + * @param string $id The cache id. + * @param mixed $data The cache entry/data. + * @param int $lifeTime The lifetime in number of seconds for this cache entry. + * If zero (the default), the entry never expires (although it may be deleted from the cache + * to make place for other entries). + * + * @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise. + */ + public function save($id, $data, $lifeTime = 0); + + /** + * Deletes a cache entry. + * + * @param string $id The cache id. + * + * @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise. + * Deleting a non-existing entry is considered successful. + */ + public function delete($id); + + /** + * Retrieves cached information from the data store. + * + * The server's statistics array has the following values: + * + * - hits + * Number of keys that have been requested and found present. + * + * - misses + * Number of items that have been requested and not found. + * + * - uptime + * Time that the server is running. + * + * - memory_usage + * Memory used by this server to store items. + * + * - memory_available + * Memory allowed to use for storage. + * + * @since 2.2 + * + * @return array|null An associative array with server's statistics if available, NULL otherwise. + */ + public function getStats(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php new file mode 100644 index 00000000..9f579237 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php @@ -0,0 +1,312 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Base class for cache provider implementations. + * + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Fabio B. Silva + */ +abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, MultiGetCache, MultiPutCache +{ + const DOCTRINE_NAMESPACE_CACHEKEY = 'DoctrineNamespaceCacheKey[%s]'; + + /** + * The namespace to prefix all cache ids with. + * + * @var string + */ + private $namespace = ''; + + /** + * The namespace version. + * + * @var integer|null + */ + private $namespaceVersion; + + /** + * Sets the namespace to prefix all cache ids with. + * + * @param string $namespace + * + * @return void + */ + public function setNamespace($namespace) + { + $this->namespace = (string) $namespace; + $this->namespaceVersion = null; + } + + /** + * Retrieves the namespace that prefixes all cache ids. + * + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * {@inheritdoc} + */ + public function fetch($id) + { + return $this->doFetch($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function fetchMultiple(array $keys) + { + if (empty($keys)) { + return array(); + } + + // note: the array_combine() is in place to keep an association between our $keys and the $namespacedKeys + $namespacedKeys = array_combine($keys, array_map(array($this, 'getNamespacedId'), $keys)); + $items = $this->doFetchMultiple($namespacedKeys); + $foundItems = array(); + + // no internal array function supports this sort of mapping: needs to be iterative + // this filters and combines keys in one pass + foreach ($namespacedKeys as $requestedKey => $namespacedKey) { + if (isset($items[$namespacedKey]) || array_key_exists($namespacedKey, $items)) { + $foundItems[$requestedKey] = $items[$namespacedKey]; + } + } + + return $foundItems; + } + + /** + * {@inheritdoc} + */ + public function saveMultiple(array $keysAndValues, $lifetime = 0) + { + $namespacedKeysAndValues = array(); + foreach ($keysAndValues as $key => $value) { + $namespacedKeysAndValues[$this->getNamespacedId($key)] = $value; + } + + return $this->doSaveMultiple($namespacedKeysAndValues, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function contains($id) + { + return $this->doContains($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function save($id, $data, $lifeTime = 0) + { + return $this->doSave($this->getNamespacedId($id), $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + public function delete($id) + { + return $this->doDelete($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function getStats() + { + return $this->doGetStats(); + } + + /** + * {@inheritDoc} + */ + public function flushAll() + { + return $this->doFlush(); + } + + /** + * {@inheritDoc} + */ + public function deleteAll() + { + $namespaceCacheKey = $this->getNamespaceCacheKey(); + $namespaceVersion = $this->getNamespaceVersion() + 1; + + if ($this->doSave($namespaceCacheKey, $namespaceVersion)) { + $this->namespaceVersion = $namespaceVersion; + + return true; + } + + return false; + } + + /** + * Prefixes the passed id with the configured namespace value. + * + * @param string $id The id to namespace. + * + * @return string The namespaced id. + */ + private function getNamespacedId($id) + { + $namespaceVersion = $this->getNamespaceVersion(); + + return sprintf('%s[%s][%s]', $this->namespace, $id, $namespaceVersion); + } + + /** + * Returns the namespace cache key. + * + * @return string + */ + private function getNamespaceCacheKey() + { + return sprintf(self::DOCTRINE_NAMESPACE_CACHEKEY, $this->namespace); + } + + /** + * Returns the namespace version. + * + * @return integer + */ + private function getNamespaceVersion() + { + if (null !== $this->namespaceVersion) { + return $this->namespaceVersion; + } + + $namespaceCacheKey = $this->getNamespaceCacheKey(); + $this->namespaceVersion = $this->doFetch($namespaceCacheKey) ?: 1; + + return $this->namespaceVersion; + } + + /** + * Default implementation of doFetchMultiple. Each driver that supports multi-get should owerwrite it. + * + * @param array $keys Array of keys to retrieve from cache + * @return array Array of values retrieved for the given keys. + */ + protected function doFetchMultiple(array $keys) + { + $returnValues = array(); + + foreach ($keys as $key) { + if (false !== ($item = $this->doFetch($key)) || $this->doContains($key)) { + $returnValues[$key] = $item; + } + } + + return $returnValues; + } + + /** + * Fetches an entry from the cache. + * + * @param string $id The id of the cache entry to fetch. + * + * @return mixed|false The cached data or FALSE, if no cache entry exists for the given id. + */ + abstract protected function doFetch($id); + + /** + * Tests if an entry exists in the cache. + * + * @param string $id The cache id of the entry to check for. + * + * @return bool TRUE if a cache entry exists for the given cache id, FALSE otherwise. + */ + abstract protected function doContains($id); + + /** + * Default implementation of doSaveMultiple. Each driver that supports multi-put should override it. + * + * @param array $keysAndValues Array of keys and values to save in cache + * @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these + * cache entries (0 => infinite lifeTime). + * + * @return bool TRUE if the operation was successful, FALSE if it wasn't. + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + $success = true; + + foreach ($keysAndValues as $key => $value) { + if (!$this->doSave($key, $value, $lifetime)) { + $success = false; + } + } + + return $success; + } + + /** + * Puts data into the cache. + * + * @param string $id The cache id. + * @param string $data The cache entry/data. + * @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this + * cache entry (0 => infinite lifeTime). + * + * @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise. + */ + abstract protected function doSave($id, $data, $lifeTime = 0); + + /** + * Deletes a cache entry. + * + * @param string $id The cache id. + * + * @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise. + */ + abstract protected function doDelete($id); + + /** + * Flushes all cache entries. + * + * @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise. + */ + abstract protected function doFlush(); + + /** + * Retrieves cached information from the data store. + * + * @since 2.2 + * + * @return array|null An associative array with server's statistics if available, NULL otherwise. + */ + abstract protected function doGetStats(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php new file mode 100644 index 00000000..96c9b547 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php @@ -0,0 +1,147 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Cache provider that allows to easily chain multiple cache providers + * + * @author Michaël Gallego + */ +class ChainCache extends CacheProvider +{ + /** + * @var CacheProvider[] + */ + private $cacheProviders = array(); + + /** + * Constructor + * + * @param CacheProvider[] $cacheProviders + */ + public function __construct($cacheProviders = array()) + { + $this->cacheProviders = $cacheProviders; + } + + /** + * {@inheritDoc} + */ + public function setNamespace($namespace) + { + parent::setNamespace($namespace); + + foreach ($this->cacheProviders as $cacheProvider) { + $cacheProvider->setNamespace($namespace); + } + } + + /** + * {@inheritDoc} + */ + protected function doFetch($id) + { + foreach ($this->cacheProviders as $key => $cacheProvider) { + if ($cacheProvider->doContains($id)) { + $value = $cacheProvider->doFetch($id); + + // We populate all the previous cache layers (that are assumed to be faster) + for ($subKey = $key - 1 ; $subKey >= 0 ; $subKey--) { + $this->cacheProviders[$subKey]->doSave($id, $value); + } + + return $value; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + protected function doContains($id) + { + foreach ($this->cacheProviders as $cacheProvider) { + if ($cacheProvider->doContains($id)) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $stored = true; + + foreach ($this->cacheProviders as $cacheProvider) { + $stored = $cacheProvider->doSave($id, $data, $lifeTime) && $stored; + } + + return $stored; + } + + /** + * {@inheritDoc} + */ + protected function doDelete($id) + { + $deleted = true; + + foreach ($this->cacheProviders as $cacheProvider) { + $deleted = $cacheProvider->doDelete($id) && $deleted; + } + + return $deleted; + } + + /** + * {@inheritDoc} + */ + protected function doFlush() + { + $flushed = true; + + foreach ($this->cacheProviders as $cacheProvider) { + $flushed = $cacheProvider->doFlush() && $flushed; + } + + return $flushed; + } + + /** + * {@inheritDoc} + */ + protected function doGetStats() + { + // We return all the stats from all adapters + $stats = array(); + + foreach ($this->cacheProviders as $cacheProvider) { + $stats[] = $cacheProvider->doGetStats(); + } + + return $stats; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php new file mode 100644 index 00000000..3a91eaf3 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php @@ -0,0 +1,40 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Interface for cache that can be flushed. + * + * Intended to be used for partial clearing of a cache namespace. For a more + * global "flushing", see {@see FlushableCache}. + * + * @link www.doctrine-project.org + * @since 1.4 + * @author Adirelle + */ +interface ClearableCache +{ + /** + * Deletes all cache entries in the current cache namespace. + * + * @return bool TRUE if the cache entries were successfully deleted, FALSE otherwise. + */ + public function deleteAll(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php new file mode 100644 index 00000000..c21691df --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php @@ -0,0 +1,121 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use \Couchbase; + +/** + * Couchbase cache provider. + * + * @link www.doctrine-project.org + * @since 2.4 + * @author Michael Nitschinger + */ +class CouchbaseCache extends CacheProvider +{ + /** + * @var Couchbase|null + */ + private $couchbase; + + /** + * Sets the Couchbase instance to use. + * + * @param Couchbase $couchbase + * + * @return void + */ + public function setCouchbase(Couchbase $couchbase) + { + $this->couchbase = $couchbase; + } + + /** + * Gets the Couchbase instance used by the cache. + * + * @return Couchbase|null + */ + public function getCouchbase() + { + return $this->couchbase; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->couchbase->get($id) ?: false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return (null !== $this->couchbase->get($id)); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 30 * 24 * 3600) { + $lifeTime = time() + $lifeTime; + } + return $this->couchbase->set($id, $data, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->couchbase->delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->couchbase->flush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $stats = $this->couchbase->getStats(); + $servers = $this->couchbase->getServers(); + $server = explode(":", $servers[0]); + $key = $server[0] . ":" . "11210"; + $stats = $stats[$key]; + return array( + Cache::STATS_HITS => $stats['get_hits'], + Cache::STATS_MISSES => $stats['get_misses'], + Cache::STATS_UPTIME => $stats['uptime'], + Cache::STATS_MEMORY_USAGE => $stats['bytes'], + Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php new file mode 100644 index 00000000..b2e0427e --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php @@ -0,0 +1,286 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Base file cache driver. + * + * @since 2.3 + * @author Fabio B. Silva + * @author Tobias Schultze + */ +abstract class FileCache extends CacheProvider +{ + /** + * The cache directory. + * + * @var string + */ + protected $directory; + + /** + * The cache file extension. + * + * @var string + */ + private $extension; + + /** + * @var int + */ + private $umask; + + /** + * @var int + */ + private $directoryStringLength; + + /** + * @var int + */ + private $extensionStringLength; + + /** + * @var bool + */ + private $isRunningOnWindows; + + /** + * Constructor. + * + * @param string $directory The cache directory. + * @param string $extension The cache file extension. + * + * @throws \InvalidArgumentException + */ + public function __construct($directory, $extension = '', $umask = 0002) + { + // YES, this needs to be *before* createPathIfNeeded() + if ( ! is_int($umask)) { + throw new \InvalidArgumentException(sprintf( + 'The umask parameter is required to be integer, was: %s', + gettype($umask) + )); + } + $this->umask = $umask; + + if ( ! $this->createPathIfNeeded($directory)) { + throw new \InvalidArgumentException(sprintf( + 'The directory "%s" does not exist and could not be created.', + $directory + )); + } + + if ( ! is_writable($directory)) { + throw new \InvalidArgumentException(sprintf( + 'The directory "%s" is not writable.', + $directory + )); + } + + // YES, this needs to be *after* createPathIfNeeded() + $this->directory = realpath($directory); + $this->extension = (string) $extension; + + $this->directoryStringLength = strlen($this->directory); + $this->extensionStringLength = strlen($this->extension); + $this->isRunningOnWindows = defined('PHP_WINDOWS_VERSION_BUILD'); + } + + /** + * Gets the cache directory. + * + * @return string + */ + public function getDirectory() + { + return $this->directory; + } + + /** + * Gets the cache file extension. + * + * @return string + */ + public function getExtension() + { + return $this->extension; + } + + /** + * @param string $id + * + * @return string + */ + protected function getFilename($id) + { + $hash = hash('sha256', $id); + + // This ensures that the filename is unique and that there are no invalid chars in it. + if ( + '' === $id + || ((strlen($id) * 2 + $this->extensionStringLength) > 255) + || ($this->isRunningOnWindows && ($this->directoryStringLength + 4 + strlen($id) * 2 + $this->extensionStringLength) > 258) + ) { + // Most filesystems have a limit of 255 chars for each path component. On Windows the the whole path is limited + // to 260 chars (including terminating null char). Using long UNC ("\\?\" prefix) does not work with the PHP API. + // And there is a bug in PHP (https://bugs.php.net/bug.php?id=70943) with path lengths of 259. + // So if the id in hex representation would surpass the limit, we use the hash instead. The prefix prevents + // collisions between the hash and bin2hex. + $filename = '_' . $hash; + } else { + $filename = bin2hex($id); + } + + return $this->directory + . DIRECTORY_SEPARATOR + . substr($hash, 0, 2) + . DIRECTORY_SEPARATOR + . $filename + . $this->extension; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + $filename = $this->getFilename($id); + + return @unlink($filename) || ! file_exists($filename); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + foreach ($this->getIterator() as $name => $file) { + if ($file->isDir()) { + // Remove the intermediate directories which have been created to balance the tree. It only takes effect + // if the directory is empty. If several caches share the same directory but with different file extensions, + // the other ones are not removed. + @rmdir($name); + } elseif ($this->isFilenameEndingWithExtension($name)) { + // If an extension is set, only remove files which end with the given extension. + // If no extension is set, we have no other choice than removing everything. + @unlink($name); + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $usage = 0; + foreach ($this->getIterator() as $name => $file) { + if (! $file->isDir() && $this->isFilenameEndingWithExtension($name)) { + $usage += $file->getSize(); + } + } + + $free = disk_free_space($this->directory); + + return array( + Cache::STATS_HITS => null, + Cache::STATS_MISSES => null, + Cache::STATS_UPTIME => null, + Cache::STATS_MEMORY_USAGE => $usage, + Cache::STATS_MEMORY_AVAILABLE => $free, + ); + } + + /** + * Create path if needed. + * + * @param string $path + * @return bool TRUE on success or if path already exists, FALSE if path cannot be created. + */ + private function createPathIfNeeded($path) + { + if ( ! is_dir($path)) { + if (false === @mkdir($path, 0777 & (~$this->umask), true) && !is_dir($path)) { + return false; + } + } + + return true; + } + + /** + * Writes a string content to file in an atomic way. + * + * @param string $filename Path to the file where to write the data. + * @param string $content The content to write + * + * @return bool TRUE on success, FALSE if path cannot be created, if path is not writable or an any other error. + */ + protected function writeFile($filename, $content) + { + $filepath = pathinfo($filename, PATHINFO_DIRNAME); + + if ( ! $this->createPathIfNeeded($filepath)) { + return false; + } + + if ( ! is_writable($filepath)) { + return false; + } + + $tmpFile = tempnam($filepath, 'swap'); + @chmod($tmpFile, 0666 & (~$this->umask)); + + if (file_put_contents($tmpFile, $content) !== false) { + if (@rename($tmpFile, $filename)) { + return true; + } + + @unlink($tmpFile); + } + + return false; + } + + /** + * @return \Iterator + */ + private function getIterator() + { + return new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), + \RecursiveIteratorIterator::CHILD_FIRST + ); + } + + /** + * @param string $name The filename + * + * @return bool + */ + private function isFilenameEndingWithExtension($name) + { + return '' === $this->extension + || strrpos($name, $this->extension) === (strlen($name) - $this->extensionStringLength); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php new file mode 100644 index 00000000..d988294f --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php @@ -0,0 +1,111 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Filesystem cache driver. + * + * @since 2.3 + * @author Fabio B. Silva + */ +class FilesystemCache extends FileCache +{ + const EXTENSION = '.doctrinecache.data'; + + /** + * {@inheritdoc} + */ + public function __construct($directory, $extension = self::EXTENSION, $umask = 0002) + { + parent::__construct($directory, $extension, $umask); + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $data = ''; + $lifetime = -1; + $filename = $this->getFilename($id); + + if ( ! is_file($filename)) { + return false; + } + + $resource = fopen($filename, "r"); + + if (false !== ($line = fgets($resource))) { + $lifetime = (int) $line; + } + + if ($lifetime !== 0 && $lifetime < time()) { + fclose($resource); + + return false; + } + + while (false !== ($line = fgets($resource))) { + $data .= $line; + } + + fclose($resource); + + return unserialize($data); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $lifetime = -1; + $filename = $this->getFilename($id); + + if ( ! is_file($filename)) { + return false; + } + + $resource = fopen($filename, "r"); + + if (false !== ($line = fgets($resource))) { + $lifetime = (int) $line; + } + + fclose($resource); + + return $lifetime === 0 || $lifetime > time(); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 0) { + $lifeTime = time() + $lifeTime; + } + + $data = serialize($data); + $filename = $this->getFilename($id); + + return $this->writeFile($filename, $lifeTime . PHP_EOL . $data); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php new file mode 100644 index 00000000..4311d4f5 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php @@ -0,0 +1,37 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Interface for cache that can be flushed. + * + * @link www.doctrine-project.org + * @since 1.4 + * @author Adirelle + */ +interface FlushableCache +{ + /** + * Flushes all cache entries, globally. + * + * @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise. + */ + public function flushAll(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php new file mode 100644 index 00000000..8afaeeac --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php @@ -0,0 +1,126 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use \Memcache; + +/** + * Memcache cache provider. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class MemcacheCache extends CacheProvider +{ + /** + * @var Memcache|null + */ + private $memcache; + + /** + * Sets the memcache instance to use. + * + * @param Memcache $memcache + * + * @return void + */ + public function setMemcache(Memcache $memcache) + { + $this->memcache = $memcache; + } + + /** + * Gets the memcache instance used by the cache. + * + * @return Memcache|null + */ + public function getMemcache() + { + return $this->memcache; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->memcache->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $flags = null; + $this->memcache->get($id, $flags); + + //if memcache has changed the value of "flags", it means the value exists + return ($flags !== null); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 30 * 24 * 3600) { + $lifeTime = time() + $lifeTime; + } + return $this->memcache->set($id, $data, 0, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + // Memcache::delete() returns false if entry does not exist + return $this->memcache->delete($id) || ! $this->doContains($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->memcache->flush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $stats = $this->memcache->getStats(); + return array( + Cache::STATS_HITS => $stats['get_hits'], + Cache::STATS_MISSES => $stats['get_misses'], + Cache::STATS_UPTIME => $stats['uptime'], + Cache::STATS_MEMORY_USAGE => $stats['bytes'], + Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php new file mode 100644 index 00000000..dc4016b5 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php @@ -0,0 +1,146 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use \Memcached; + +/** + * Memcached cache provider. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class MemcachedCache extends CacheProvider +{ + /** + * @var Memcached|null + */ + private $memcached; + + /** + * Sets the memcache instance to use. + * + * @param Memcached $memcached + * + * @return void + */ + public function setMemcached(Memcached $memcached) + { + $this->memcached = $memcached; + } + + /** + * Gets the memcached instance used by the cache. + * + * @return Memcached|null + */ + public function getMemcached() + { + return $this->memcached; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->memcached->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + return $this->memcached->getMulti($keys) ?: []; + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + if ($lifetime > 30 * 24 * 3600) { + $lifetime = time() + $lifetime; + } + + return $this->memcached->setMulti($keysAndValues, $lifetime); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return false !== $this->memcached->get($id) + || $this->memcached->getResultCode() !== Memcached::RES_NOTFOUND; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 30 * 24 * 3600) { + $lifeTime = time() + $lifeTime; + } + return $this->memcached->set($id, $data, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->memcached->delete($id) + || $this->memcached->getResultCode() === Memcached::RES_NOTFOUND; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->memcached->flush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $stats = $this->memcached->getStats(); + $servers = $this->memcached->getServerList(); + $key = $servers[0]['host'] . ':' . $servers[0]['port']; + $stats = $stats[$key]; + return array( + Cache::STATS_HITS => $stats['get_hits'], + Cache::STATS_MISSES => $stats['get_misses'], + Cache::STATS_UPTIME => $stats['uptime'], + Cache::STATS_MEMORY_USAGE => $stats['bytes'], + Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php new file mode 100644 index 00000000..75fe0ca1 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php @@ -0,0 +1,197 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use MongoBinData; +use MongoCollection; +use MongoCursorException; +use MongoDate; + +/** + * MongoDB cache provider. + * + * @since 1.1 + * @author Jeremy Mikola + */ +class MongoDBCache extends CacheProvider +{ + /** + * The data field will store the serialized PHP value. + */ + const DATA_FIELD = 'd'; + + /** + * The expiration field will store a MongoDate value indicating when the + * cache entry should expire. + * + * With MongoDB 2.2+, entries can be automatically deleted by MongoDB by + * indexing this field with the "expireAfterSeconds" option equal to zero. + * This will direct MongoDB to regularly query for and delete any entries + * whose date is older than the current time. Entries without a date value + * in this field will be ignored. + * + * The cache provider will also check dates on its own, in case expired + * entries are fetched before MongoDB's TTLMonitor pass can expire them. + * + * @see http://docs.mongodb.org/manual/tutorial/expire-data/ + */ + const EXPIRATION_FIELD = 'e'; + + /** + * @var MongoCollection + */ + private $collection; + + /** + * Constructor. + * + * This provider will default to the write concern and read preference + * options set on the MongoCollection instance (or inherited from MongoDB or + * MongoClient). Using an unacknowledged write concern (< 1) may make the + * return values of delete() and save() unreliable. Reading from secondaries + * may make contain() and fetch() unreliable. + * + * @see http://www.php.net/manual/en/mongo.readpreferences.php + * @see http://www.php.net/manual/en/mongo.writeconcerns.php + * @param MongoCollection $collection + */ + public function __construct(MongoCollection $collection) + { + $this->collection = $collection; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $document = $this->collection->findOne(array('_id' => $id), array(self::DATA_FIELD, self::EXPIRATION_FIELD)); + + if ($document === null) { + return false; + } + + if ($this->isExpired($document)) { + $this->doDelete($id); + return false; + } + + return unserialize($document[self::DATA_FIELD]->bin); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $document = $this->collection->findOne(array('_id' => $id), array(self::EXPIRATION_FIELD)); + + if ($document === null) { + return false; + } + + if ($this->isExpired($document)) { + $this->doDelete($id); + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + try { + $result = $this->collection->update( + array('_id' => $id), + array('$set' => array( + self::EXPIRATION_FIELD => ($lifeTime > 0 ? new MongoDate(time() + $lifeTime) : null), + self::DATA_FIELD => new MongoBinData(serialize($data), MongoBinData::BYTE_ARRAY), + )), + array('upsert' => true, 'multiple' => false) + ); + } catch (MongoCursorException $e) { + return false; + } + + return isset($result['ok']) ? $result['ok'] == 1 : true; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + $result = $this->collection->remove(array('_id' => $id)); + + return isset($result['ok']) ? $result['ok'] == 1 : true; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + // Use remove() in lieu of drop() to maintain any collection indexes + $result = $this->collection->remove(); + + return isset($result['ok']) ? $result['ok'] == 1 : true; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $serverStatus = $this->collection->db->command(array( + 'serverStatus' => 1, + 'locks' => 0, + 'metrics' => 0, + 'recordStats' => 0, + 'repl' => 0, + )); + + $collStats = $this->collection->db->command(array('collStats' => 1)); + + return array( + Cache::STATS_HITS => null, + Cache::STATS_MISSES => null, + Cache::STATS_UPTIME => (isset($serverStatus['uptime']) ? (int) $serverStatus['uptime'] : null), + Cache::STATS_MEMORY_USAGE => (isset($collStats['size']) ? (int) $collStats['size'] : null), + Cache::STATS_MEMORY_AVAILABLE => null, + ); + } + + /** + * Check if the document is expired. + * + * @param array $document + * + * @return bool + */ + private function isExpired(array $document) + { + return isset($document[self::EXPIRATION_FIELD]) && + $document[self::EXPIRATION_FIELD] instanceof MongoDate && + $document[self::EXPIRATION_FIELD]->sec < time(); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiGetCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiGetCache.php new file mode 100644 index 00000000..df7146d7 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiGetCache.php @@ -0,0 +1,39 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Interface for cache drivers that allows to get many items at once. + * + * @link www.doctrine-project.org + * @since 1.4 + * @author Asmir Mustafic + */ +interface MultiGetCache +{ + /** + * Returns an associative array of values for keys is found in cache. + * + * @param string[] $keys Array of keys to retrieve from cache + * @return mixed[] Array of retrieved values, indexed by the specified keys. + * Values that couldn't be retrieved are not contained in this array. + */ + function fetchMultiple(array $keys); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiPutCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiPutCache.php new file mode 100644 index 00000000..bf87ea9f --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiPutCache.php @@ -0,0 +1,41 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Interface for cache drivers that allows to put many items at once. + * + * @link www.doctrine-project.org + * @since 1.6 + * @author Daniel Gorgan + */ +interface MultiPutCache +{ + /** + * Returns a boolean value indicating if the operation succeeded. + * + * @param array $keysAndValues Array of keys and values to save in cache + * @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these + * cache entries (0 => infinite lifeTime). + * + * @return bool TRUE if the operation was successful, FALSE if it wasn't. + */ + function saveMultiple(array $keysAndValues, $lifetime = 0); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php new file mode 100644 index 00000000..5e751967 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php @@ -0,0 +1,120 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Php file cache driver. + * + * @since 2.3 + * @author Fabio B. Silva + */ +class PhpFileCache extends FileCache +{ + const EXTENSION = '.doctrinecache.php'; + + /** + * {@inheritdoc} + */ + public function __construct($directory, $extension = self::EXTENSION, $umask = 0002) + { + parent::__construct($directory, $extension, $umask); + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $value = $this->includeFileForId($id); + + if (! $value) { + return false; + } + + if ($value['lifetime'] !== 0 && $value['lifetime'] < time()) { + return false; + } + + return $value['data']; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $value = $this->includeFileForId($id); + + if (! $value) { + return false; + } + + return $value['lifetime'] === 0 || $value['lifetime'] > time(); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 0) { + $lifeTime = time() + $lifeTime; + } + + if (is_object($data) && ! method_exists($data, '__set_state')) { + throw new \InvalidArgumentException( + "Invalid argument given, PhpFileCache only allows objects that implement __set_state() " . + "and fully support var_export(). You can use the FilesystemCache to save arbitrary object " . + "graphs using serialize()/deserialize()." + ); + } + + $filename = $this->getFilename($id); + + $value = array( + 'lifetime' => $lifeTime, + 'data' => $data + ); + + $value = var_export($value, true); + $code = sprintf('writeFile($filename, $code); + } + + /** + * @param string $id + * + * @return array|false + */ + private function includeFileForId($id) + { + $fileName = $this->getFilename($id); + + // note: error suppression is still faster than `file_exists`, `is_file` and `is_readable` + $value = @include $fileName; + + if (! isset($value['lifetime'])) { + return false; + } + + return $value; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php new file mode 100644 index 00000000..bcd27497 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php @@ -0,0 +1,136 @@ + + */ +class PredisCache extends CacheProvider +{ + /** + * @var ClientInterface + */ + private $client; + + /** + * @param ClientInterface $client + * + * @return void + */ + public function __construct(ClientInterface $client) + { + $this->client = $client; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $result = $this->client->get($id); + if (null === $result) { + return false; + } + + return unserialize($result); + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + $fetchedItems = call_user_func_array(array($this->client, 'mget'), $keys); + + return array_map('unserialize', array_filter(array_combine($keys, $fetchedItems))); + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + if ($lifetime) { + $success = true; + + // Keys have lifetime, use SETEX for each of them + foreach ($keysAndValues as $key => $value) { + $response = $this->client->setex($key, $lifetime, serialize($value)); + + if ((string) $response != 'OK') { + $success = false; + } + } + + return $success; + } + + // No lifetime, use MSET + $response = $this->client->mset(array_map(function ($value) { + return serialize($value); + }, $keysAndValues)); + + return (string) $response == 'OK'; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return (bool) $this->client->exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $data = serialize($data); + if ($lifeTime > 0) { + $response = $this->client->setex($id, $lifeTime, $data); + } else { + $response = $this->client->set($id, $data); + } + + return $response === true || $response == 'OK'; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->client->del($id) >= 0; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $response = $this->client->flushdb(); + + return $response === true || $response == 'OK'; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = $this->client->info(); + + return array( + Cache::STATS_HITS => $info['Stats']['keyspace_hits'], + Cache::STATS_MISSES => $info['Stats']['keyspace_misses'], + Cache::STATS_UPTIME => $info['Server']['uptime_in_seconds'], + Cache::STATS_MEMORY_USAGE => $info['Memory']['used_memory'], + Cache::STATS_MEMORY_AVAILABLE => false + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php new file mode 100644 index 00000000..22add667 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php @@ -0,0 +1,180 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use Redis; + +/** + * Redis cache provider. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Osman Ungur + */ +class RedisCache extends CacheProvider +{ + /** + * @var Redis|null + */ + private $redis; + + /** + * Sets the redis instance to use. + * + * @param Redis $redis + * + * @return void + */ + public function setRedis(Redis $redis) + { + $redis->setOption(Redis::OPT_SERIALIZER, $this->getSerializerValue()); + $this->redis = $redis; + } + + /** + * Gets the redis instance used by the cache. + * + * @return Redis|null + */ + public function getRedis() + { + return $this->redis; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->redis->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + $fetchedItems = array_combine($keys, $this->redis->mget($keys)); + + // Redis mget returns false for keys that do not exist. So we need to filter those out unless it's the real data. + $foundItems = array(); + + foreach ($fetchedItems as $key => $value) { + if (false !== $value || $this->redis->exists($key)) { + $foundItems[$key] = $value; + } + } + + return $foundItems; + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + if ($lifetime) { + $success = true; + + // Keys have lifetime, use SETEX for each of them + foreach ($keysAndValues as $key => $value) { + if (!$this->redis->setex($key, $lifetime, $value)) { + $success = false; + } + } + + return $success; + } + + // No lifetime, use MSET + return (bool) $this->redis->mset($keysAndValues); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return $this->redis->exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 0) { + return $this->redis->setex($id, $lifeTime, $data); + } + + return $this->redis->set($id, $data); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->redis->delete($id) >= 0; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->redis->flushDB(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = $this->redis->info(); + return array( + Cache::STATS_HITS => $info['keyspace_hits'], + Cache::STATS_MISSES => $info['keyspace_misses'], + Cache::STATS_UPTIME => $info['uptime_in_seconds'], + Cache::STATS_MEMORY_USAGE => $info['used_memory'], + Cache::STATS_MEMORY_AVAILABLE => false + ); + } + + /** + * Returns the serializer constant to use. If Redis is compiled with + * igbinary support, that is used. Otherwise the default PHP serializer is + * used. + * + * @return integer One of the Redis::SERIALIZER_* constants + */ + protected function getSerializerValue() + { + if (defined('HHVM_VERSION')) { + return Redis::SERIALIZER_PHP; + } + + if (defined('Redis::SERIALIZER_IGBINARY') && extension_loaded('igbinary')) { + return Redis::SERIALIZER_IGBINARY; + } + + return Redis::SERIALIZER_PHP; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php new file mode 100644 index 00000000..0baa3f25 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php @@ -0,0 +1,250 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use Riak\Bucket; +use Riak\Connection; +use Riak\Input; +use Riak\Exception; +use Riak\Object; + +/** + * Riak cache provider. + * + * @link www.doctrine-project.org + * @since 1.1 + * @author Guilherme Blanco + */ +class RiakCache extends CacheProvider +{ + const EXPIRES_HEADER = 'X-Riak-Meta-Expires'; + + /** + * @var \Riak\Bucket + */ + private $bucket; + + /** + * Sets the riak bucket instance to use. + * + * @param \Riak\Bucket $bucket + */ + public function __construct(Bucket $bucket) + { + $this->bucket = $bucket; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + try { + $response = $this->bucket->get($id); + + // No objects found + if ( ! $response->hasObject()) { + return false; + } + + // Check for attempted siblings + $object = ($response->hasSiblings()) + ? $this->resolveConflict($id, $response->getVClock(), $response->getObjectList()) + : $response->getFirstObject(); + + // Check for expired object + if ($this->isExpired($object)) { + $this->bucket->delete($object); + + return false; + } + + return unserialize($object->getContent()); + } catch (Exception\RiakException $e) { + // Covers: + // - Riak\ConnectionException + // - Riak\CommunicationException + // - Riak\UnexpectedResponseException + // - Riak\NotFoundException + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + try { + // We only need the HEAD, not the entire object + $input = new Input\GetInput(); + + $input->setReturnHead(true); + + $response = $this->bucket->get($id, $input); + + // No objects found + if ( ! $response->hasObject()) { + return false; + } + + $object = $response->getFirstObject(); + + // Check for expired object + if ($this->isExpired($object)) { + $this->bucket->delete($object); + + return false; + } + + return true; + } catch (Exception\RiakException $e) { + // Do nothing + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + try { + $object = new Object($id); + + $object->setContent(serialize($data)); + + if ($lifeTime > 0) { + $object->addMetadata(self::EXPIRES_HEADER, (string) (time() + $lifeTime)); + } + + $this->bucket->put($object); + + return true; + } catch (Exception\RiakException $e) { + // Do nothing + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + try { + $this->bucket->delete($id); + + return true; + } catch (Exception\BadArgumentsException $e) { + // Key did not exist on cluster already + } catch (Exception\RiakException $e) { + // Covers: + // - Riak\Exception\ConnectionException + // - Riak\Exception\CommunicationException + // - Riak\Exception\UnexpectedResponseException + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + try { + $keyList = $this->bucket->getKeyList(); + + foreach ($keyList as $key) { + $this->bucket->delete($key); + } + + return true; + } catch (Exception\RiakException $e) { + // Do nothing + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + // Only exposed through HTTP stats API, not Protocol Buffers API + return null; + } + + /** + * Check if a given Riak Object have expired. + * + * @param \Riak\Object $object + * + * @return bool + */ + private function isExpired(Object $object) + { + $metadataMap = $object->getMetadataMap(); + + return isset($metadataMap[self::EXPIRES_HEADER]) + && $metadataMap[self::EXPIRES_HEADER] < time(); + } + + /** + * On-read conflict resolution. Applied approach here is last write wins. + * Specific needs may override this method to apply alternate conflict resolutions. + * + * {@internal Riak does not attempt to resolve a write conflict, and store + * it as sibling of conflicted one. By following this approach, it is up to + * the next read to resolve the conflict. When this happens, your fetched + * object will have a list of siblings (read as a list of objects). + * In our specific case, we do not care about the intermediate ones since + * they are all the same read from storage, and we do apply a last sibling + * (last write) wins logic. + * If by any means our resolution generates another conflict, it'll up to + * next read to properly solve it.} + * + * @param string $id + * @param string $vClock + * @param array $objectList + * + * @return \Riak\Object + */ + protected function resolveConflict($id, $vClock, array $objectList) + { + // Our approach here is last-write wins + $winner = $objectList[count($objectList)]; + + $putInput = new Input\PutInput(); + $putInput->setVClock($vClock); + + $mergedObject = new Object($id); + $mergedObject->setContent($winner->getContent()); + + $this->bucket->put($mergedObject, $putInput); + + return $mergedObject; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php new file mode 100644 index 00000000..0bf6e4d4 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php @@ -0,0 +1,220 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use SQLite3; +use SQLite3Result; + +/** + * SQLite3 cache provider. + * + * @since 1.4 + * @author Jake Bell + */ +class SQLite3Cache extends CacheProvider +{ + /** + * The ID field will store the cache key. + */ + const ID_FIELD = 'k'; + + /** + * The data field will store the serialized PHP value. + */ + const DATA_FIELD = 'd'; + + /** + * The expiration field will store a date value indicating when the + * cache entry should expire. + */ + const EXPIRATION_FIELD = 'e'; + + /** + * @var SQLite3 + */ + private $sqlite; + + /** + * @var string + */ + private $table; + + /** + * Constructor. + * + * Calling the constructor will ensure that the database file and table + * exist and will create both if they don't. + * + * @param SQLite3 $sqlite + * @param string $table + */ + public function __construct(SQLite3 $sqlite, $table) + { + $this->sqlite = $sqlite; + $this->table = (string) $table; + + list($id, $data, $exp) = $this->getFields(); + + return $this->sqlite->exec(sprintf( + 'CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY NOT NULL, %s BLOB, %s INTEGER)', + $table, + $id, + $data, + $exp + )); + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + if ($item = $this->findById($id)) { + return unserialize($item[self::DATA_FIELD]); + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return null !== $this->findById($id, false); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $statement = $this->sqlite->prepare(sprintf( + 'INSERT OR REPLACE INTO %s (%s) VALUES (:id, :data, :expire)', + $this->table, + implode(',', $this->getFields()) + )); + + $statement->bindValue(':id', $id); + $statement->bindValue(':data', serialize($data), SQLITE3_BLOB); + $statement->bindValue(':expire', $lifeTime > 0 ? time() + $lifeTime : null); + + return $statement->execute() instanceof SQLite3Result; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + list($idField) = $this->getFields(); + + $statement = $this->sqlite->prepare(sprintf( + 'DELETE FROM %s WHERE %s = :id', + $this->table, + $idField + )); + + $statement->bindValue(':id', $id); + + return $statement->execute() instanceof SQLite3Result; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->sqlite->exec(sprintf('DELETE FROM %s', $this->table)); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + // no-op. + } + + /** + * Find a single row by ID. + * + * @param mixed $id + * @param bool $includeData + * + * @return array|null + */ + private function findById($id, $includeData = true) + { + list($idField) = $fields = $this->getFields(); + + if (!$includeData) { + $key = array_search(static::DATA_FIELD, $fields); + unset($fields[$key]); + } + + $statement = $this->sqlite->prepare(sprintf( + 'SELECT %s FROM %s WHERE %s = :id LIMIT 1', + implode(',', $fields), + $this->table, + $idField + )); + + $statement->bindValue(':id', $id, SQLITE3_TEXT); + + $item = $statement->execute()->fetchArray(SQLITE3_ASSOC); + + if ($item === false) { + return null; + } + + if ($this->isExpired($item)) { + $this->doDelete($id); + + return null; + } + + return $item; + } + + /** + * Gets an array of the fields in our table. + * + * @return array + */ + private function getFields() + { + return array(static::ID_FIELD, static::DATA_FIELD, static::EXPIRATION_FIELD); + } + + /** + * Check if the item is expired. + * + * @param array $item + * + * @return bool + */ + private function isExpired(array $item) + { + return isset($item[static::EXPIRATION_FIELD]) && + $item[self::EXPIRATION_FIELD] !== null && + $item[self::EXPIRATION_FIELD] < time(); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php new file mode 100644 index 00000000..a6399ecc --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php @@ -0,0 +1,25 @@ +. + */ + +namespace Doctrine\Common\Cache; + +class Version +{ + const VERSION = '1.6.1-DEV'; +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/VoidCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/VoidCache.php new file mode 100644 index 00000000..65e8456f --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/VoidCache.php @@ -0,0 +1,78 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Void cache driver. The cache could be of use in tests where you don`t need to cache anything. + * + * @link www.doctrine-project.org + * @since 1.5 + * @author Kotlyar Maksim + */ +class VoidCache extends CacheProvider +{ + /** + * {@inheritDoc} + */ + protected function doFetch($id) + { + return false; + } + + /** + * {@inheritDoc} + */ + protected function doContains($id) + { + return false; + } + + /** + * {@inheritDoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return true; + } + + /** + * {@inheritDoc} + */ + protected function doDelete($id) + { + return true; + } + + /** + * {@inheritDoc} + */ + protected function doFlush() + { + return true; + } + + /** + * {@inheritDoc} + */ + protected function doGetStats() + { + return; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php new file mode 100644 index 00000000..8a250b29 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php @@ -0,0 +1,109 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * WinCache cache provider. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class WinCacheCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return wincache_ucache_get($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return wincache_ucache_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return wincache_ucache_set($id, $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return wincache_ucache_delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return wincache_ucache_clear(); + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + return wincache_ucache_get($keys); + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + $result = wincache_ucache_set($keysAndValues, null, $lifetime); + + return empty($result); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = wincache_ucache_info(); + $meminfo = wincache_ucache_meminfo(); + + return array( + Cache::STATS_HITS => $info['total_hit_count'], + Cache::STATS_MISSES => $info['total_miss_count'], + Cache::STATS_UPTIME => $info['total_cache_uptime'], + Cache::STATS_MEMORY_USAGE => $meminfo['memory_total'], + Cache::STATS_MEMORY_AVAILABLE => $meminfo['memory_free'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php new file mode 100644 index 00000000..a2c4ca56 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php @@ -0,0 +1,112 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Xcache cache driver. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class XcacheCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->doContains($id) ? unserialize(xcache_get($id)) : false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return xcache_isset($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return xcache_set($id, serialize($data), (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return xcache_unset($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $this->checkAuthorization(); + + xcache_clear_cache(XC_TYPE_VAR); + + return true; + } + + /** + * Checks that xcache.admin.enable_auth is Off. + * + * @return void + * + * @throws \BadMethodCallException When xcache.admin.enable_auth is On. + */ + protected function checkAuthorization() + { + if (ini_get('xcache.admin.enable_auth')) { + throw new \BadMethodCallException( + 'To use all features of \Doctrine\Common\Cache\XcacheCache, ' + . 'you must set "xcache.admin.enable_auth" to "Off" in your php.ini.' + ); + } + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $this->checkAuthorization(); + + $info = xcache_info(XC_TYPE_VAR, 0); + return array( + Cache::STATS_HITS => $info['hits'], + Cache::STATS_MISSES => $info['misses'], + Cache::STATS_UPTIME => null, + Cache::STATS_MEMORY_USAGE => $info['size'], + Cache::STATS_MEMORY_AVAILABLE => $info['avail'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php new file mode 100644 index 00000000..6e35ac82 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php @@ -0,0 +1,83 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Zend Data Cache cache driver. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Ralph Schindler + * @author Guilherme Blanco + */ +class ZendDataCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return zend_shm_cache_fetch($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return (false !== zend_shm_cache_fetch($id)); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return zend_shm_cache_store($id, $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return zend_shm_cache_delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $namespace = $this->getNamespace(); + if (empty($namespace)) { + return zend_shm_cache_clear(); + } + return zend_shm_cache_clear($namespace); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + return null; + } +} diff --git a/vendor/doctrine/cache/phpunit.xml.dist b/vendor/doctrine/cache/phpunit.xml.dist new file mode 100644 index 00000000..40cc24de --- /dev/null +++ b/vendor/doctrine/cache/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + + + + + + ./tests/Doctrine/ + + + + + + ./lib/Doctrine/ + + + diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ApcCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ApcCacheTest.php new file mode 100644 index 00000000..5becdf99 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ApcCacheTest.php @@ -0,0 +1,28 @@ +markTestSkipped('APC must be enabled for the CLI with the ini setting apc.enable_cli=1'); + } + } + + protected function _getCacheDriver() + { + return new ApcCache(); + } + + public function testLifetime() + { + $this->markTestSkipped('The APC cache TTL is not working in a single process/request. See https://bugs.php.net/bug.php?id=58084'); + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ApcuCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ApcuCacheTest.php new file mode 100644 index 00000000..27764cd7 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ApcuCacheTest.php @@ -0,0 +1,28 @@ +markTestSkipped('APC must be enabled for the CLI with the ini setting apc.enable_cli=1'); + } + } + + protected function _getCacheDriver() + { + return new ApcuCache(); + } + + public function testLifetime() + { + $this->markTestSkipped('The APC cache TTL is not working in a single process/request. See https://bugs.php.net/bug.php?id=58084'); + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ArrayCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ArrayCacheTest.php new file mode 100644 index 00000000..3be94c61 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ArrayCacheTest.php @@ -0,0 +1,52 @@ +_getCacheDriver(); + $cache->fetch('test1'); + $cache->fetch('test2'); + $cache->fetch('test3'); + + $cache->save('test1', 123); + $cache->save('test2', 123); + + $cache->fetch('test1'); + $cache->fetch('test2'); + $cache->fetch('test3'); + + $stats = $cache->getStats(); + $this->assertEquals(2, $stats[Cache::STATS_HITS]); + $this->assertEquals(5, $stats[Cache::STATS_MISSES]); // +1 for internal call to DoctrineNamespaceCacheKey + $this->assertNotNull($stats[Cache::STATS_UPTIME]); + $this->assertNull($stats[Cache::STATS_MEMORY_USAGE]); + $this->assertNull($stats[Cache::STATS_MEMORY_AVAILABLE]); + + $cache->delete('test1'); + $cache->delete('test2'); + + $cache->fetch('test1'); + $cache->fetch('test2'); + $cache->fetch('test3'); + + $stats = $cache->getStats(); + $this->assertEquals(2, $stats[Cache::STATS_HITS]); + $this->assertEquals(8, $stats[Cache::STATS_MISSES]); // +1 for internal call to DoctrineNamespaceCacheKey + } + + protected function isSharedStorage() + { + return false; + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/BaseFileCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/BaseFileCacheTest.php new file mode 100644 index 00000000..c1256ee3 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/BaseFileCacheTest.php @@ -0,0 +1,155 @@ +directory = sys_get_temp_dir() . '/doctrine_cache_'. uniqid(); + } while (file_exists($this->directory)); + } + + protected function tearDown() + { + if ( ! is_dir($this->directory)) { + return; + } + + $iterator = new RecursiveDirectoryIterator($this->directory); + + foreach (new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::CHILD_FIRST) as $file) { + if ($file->isFile()) { + @unlink($file->getRealPath()); + } elseif ($file->isDir()) { + @rmdir($file->getRealPath()); + } + } + + @rmdir($this->directory); + } + + public function testFlushAllRemovesBalancingDirectories() + { + $cache = $this->_getCacheDriver(); + + $this->assertTrue($cache->save('key1', 1)); + $this->assertTrue($cache->save('key2', 2)); + $this->assertTrue($cache->flushAll()); + + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST); + + $this->assertCount(0, $iterator); + } + + protected function isSharedStorage() + { + return false; + } + + public function getPathLengthsToTest() + { + // Windows officially supports 260 bytes including null terminator + // 258 bytes available to use due to php bug #70943 + // Windows officially supports 260 bytes including null terminator + // 259 characters is too large due to PHP bug (https://bugs.php.net/bug.php?id=70943) + // 260 characters is too large - null terminator is included in allowable length + return array( + array(257, false), + array(258, false), + array(259, true), + array(260, true) + ); + } + + private static function getBasePathForWindowsPathLengthTests($pathLength) + { + return FileCacheTest::getBasePathForWindowsPathLengthTests($pathLength); + } + + /** + * @param int $length + * @param string $basePath + * + * @return array + */ + private static function getKeyAndPathFittingLength($length, $basePath) + { + $baseDirLength = strlen($basePath); + $extensionLength = strlen('.doctrine.cache'); + $directoryLength = strlen(DIRECTORY_SEPARATOR . 'aa' . DIRECTORY_SEPARATOR); + $namespaceAndBracketLength = strlen(bin2hex("[][1]")); + $keyLength = $length + - ($baseDirLength + + $extensionLength + + $directoryLength + + $namespaceAndBracketLength); + + $key = str_repeat('a', floor($keyLength / 2)); + $namespacedKey = '[' . $key . '][1]'; + + $keyHash = hash('sha256', $namespacedKey); + + $keyPath = $basePath + . DIRECTORY_SEPARATOR + . substr($keyHash, 0, 2) + . DIRECTORY_SEPARATOR + . bin2hex($namespacedKey) + . '.doctrine.cache'; + + $hashedKeyPath = $basePath + . DIRECTORY_SEPARATOR + . substr($keyHash, 0, 2) + . DIRECTORY_SEPARATOR + . '_' . $keyHash + . '.doctrine.cache'; + + return array($key, $keyPath, $hashedKeyPath); + } + + /** + * @dataProvider getPathLengthsToTest + * + * @param int $length + * @param bool $pathShouldBeHashed + */ + public function testWindowsPathLengthLimitIsCorrectlyHandled($length, $pathShouldBeHashed) + { + $this->directory = self::getBasePathForWindowsPathLengthTests($length); + + list($key, $keyPath, $hashedKeyPath) = self::getKeyAndPathFittingLength($length, $this->directory); + + $this->assertEquals($length, strlen($keyPath), 'Unhashed path should be of correct length.'); + + $cacheClass = get_class($this->_getCacheDriver()); + /* @var $cache \Doctrine\Common\Cache\FileCache */ + $cache = new $cacheClass($this->directory, '.doctrine.cache'); + + // Trick it into thinking this is windows. + $reflClass = new \ReflectionClass(FileCache::class); + $reflProp = $reflClass->getProperty('isRunningOnWindows'); + $reflProp->setAccessible(true); + $reflProp->setValue($cache, true); + $reflProp->setAccessible(false); + + $value = uniqid('value', true); + + $cache->save($key, $value); + $this->assertEquals($value, $cache->fetch($key)); + + if ($pathShouldBeHashed) { + $this->assertFileExists($hashedKeyPath, 'Path generated for key should be hashed.'); + unlink($hashedKeyPath); + } else { + $this->assertFileExists($keyPath, 'Path generated for key should not be hashed.'); + unlink($keyPath); + } + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CacheProviderTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CacheProviderTest.php new file mode 100644 index 00000000..5e11652c --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CacheProviderTest.php @@ -0,0 +1,103 @@ +getMockForAbstractClass( + 'Doctrine\Common\Cache\CacheProvider', + array(), + '', + true, + true, + true, + array('doFetchMultiple') + ); + + $cache + ->expects($this->once()) + ->method('doFetchMultiple') + ->will($this->returnValue(array( + '[foo][1]' => 'bar', + '[bar][1]' => 'baz', + '[baz][1]' => 'tab', + ))); + + $this->assertEquals( + array('foo' => 'bar', 'bar' => 'baz'), + $cache->fetchMultiple(array('foo', 'bar')) + ); + } + + public function testFailedDeleteAllDoesNotChangeNamespaceVersion() + { + /* @var $cache \Doctrine\Common\Cache\CacheProvider|\PHPUnit_Framework_MockObject_MockObject */ + $cache = $this->getMockForAbstractClass( + 'Doctrine\Common\Cache\CacheProvider', + array(), + '', + true, + true, + true, + array('doFetch', 'doSave', 'doContains') + ); + + $cache + ->expects($this->once()) + ->method('doFetch') + ->with('DoctrineNamespaceCacheKey[]') + ->will($this->returnValue(false)); + + // doSave is only called once from deleteAll as we do not need to persist the default version in getNamespaceVersion() + $cache + ->expects($this->once()) + ->method('doSave') + ->with('DoctrineNamespaceCacheKey[]') + ->will($this->returnValue(false)); + + // After a failed deleteAll() the local namespace version is not increased (still 1). Otherwise all data written afterwards + // would be lost outside the current instance. + $cache + ->expects($this->once()) + ->method('doContains') + ->with('[key][1]') + ->will($this->returnValue(true)); + + $this->assertFalse($cache->deleteAll(), 'deleteAll() returns false when saving the namespace version fails'); + $cache->contains('key'); + } + + public function testSaveMultipleNoFail() + { + /* @var $cache \Doctrine\Common\Cache\CacheProvider|\PHPUnit_Framework_MockObject_MockObject */ + $cache = $this->getMockForAbstractClass( + 'Doctrine\Common\Cache\CacheProvider', + array(), + '', + true, + true, + true, + array('doSave') + ); + + $cache + ->expects($this->at(1)) + ->method('doSave') + ->with('[kerr][1]', 'verr', 0) + ->will($this->returnValue(false)); + + $cache + ->expects($this->at(2)) + ->method('doSave') + ->with('[kok][1]', 'vok', 0) + ->will($this->returnValue(true)); + + $cache->saveMultiple(array( + 'kerr' => 'verr', + 'kok' => 'vok', + )); + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CacheTest.php new file mode 100644 index 00000000..5dd64d7f --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CacheTest.php @@ -0,0 +1,463 @@ +_getCacheDriver(); + + // Test saving a value, checking if it exists, and fetching it back + $this->assertTrue($cache->save('key', $value)); + $this->assertTrue($cache->contains('key')); + if (is_object($value)) { + $this->assertEquals($value, $cache->fetch('key'), 'Objects retrieved from the cache must be equal but not necessarily the same reference'); + } else { + $this->assertSame($value, $cache->fetch('key'), 'Scalar and array data retrieved from the cache must be the same as the original, e.g. same type'); + } + + // Test deleting a value + $this->assertTrue($cache->delete('key')); + $this->assertFalse($cache->contains('key')); + $this->assertFalse($cache->fetch('key')); + } + + /** + * @dataProvider provideDataToCache + */ + public function testUpdateExistingEntry($value) + { + $cache = $this->_getCacheDriver(); + + $this->assertTrue($cache->save('key', 'old-value')); + $this->assertTrue($cache->contains('key')); + + $this->assertTrue($cache->save('key', $value)); + $this->assertTrue($cache->contains('key')); + if (is_object($value)) { + $this->assertEquals($value, $cache->fetch('key'), 'Objects retrieved from the cache must be equal but not necessarily the same reference'); + } else { + $this->assertSame($value, $cache->fetch('key'), 'Scalar and array data retrieved from the cache must be the same as the original, e.g. same type'); + } + } + + public function testCacheKeyIsCaseSensitive() + { + $cache = $this->_getCacheDriver(); + + $this->assertTrue($cache->save('key', 'value')); + $this->assertTrue($cache->contains('key')); + $this->assertSame('value', $cache->fetch('key')); + + $this->assertFalse($cache->contains('KEY')); + $this->assertFalse($cache->fetch('KEY')); + + $cache->delete('KEY'); + $this->assertTrue($cache->contains('key', 'Deleting cache item with different case must not affect other cache item')); + } + + public function testFetchMultiple() + { + $cache = $this->_getCacheDriver(); + $values = $this->provideDataToCache(); + $saved = array(); + + foreach ($values as $key => $value) { + $cache->save($key, $value[0]); + + $saved[$key] = $value[0]; + } + + $keys = array_keys($saved); + + $this->assertEquals( + $saved, + $cache->fetchMultiple($keys), + 'Testing fetchMultiple with different data types' + ); + $this->assertEquals( + array_slice($saved, 0, 1), + $cache->fetchMultiple(array_slice($keys, 0, 1)), + 'Testing fetchMultiple with a single key' + ); + + $keysWithNonExisting = array(); + $keysWithNonExisting[] = 'non_existing1'; + $keysWithNonExisting[] = $keys[0]; + $keysWithNonExisting[] = 'non_existing2'; + $keysWithNonExisting[] = $keys[1]; + $keysWithNonExisting[] = 'non_existing3'; + + $this->assertEquals( + array_slice($saved, 0, 2), + $cache->fetchMultiple($keysWithNonExisting), + 'Testing fetchMultiple with a subset of keys and mixed with non-existing ones' + ); + } + + public function testFetchMultipleWithNoKeys() + { + $cache = $this->_getCacheDriver(); + + $this->assertSame(array(), $cache->fetchMultiple(array())); + } + + public function testSaveMultiple() + { + $cache = $this->_getCacheDriver(); + $cache->deleteAll(); + + $data = array_map(function ($value) { + return $value[0]; + }, $this->provideDataToCache()); + + $this->assertTrue($cache->saveMultiple($data)); + + $keys = array_keys($data); + + $this->assertEquals($data, $cache->fetchMultiple($keys)); + } + + public function provideDataToCache() + { + $obj = new \stdClass(); + $obj->foo = 'bar'; + $obj2 = new \stdClass(); + $obj2->bar = 'foo'; + $obj2->obj = $obj; + $obj->obj2 = $obj2; + + return array( + 'array' => array(array('one', 2, 3.01)), + 'string' => array('value'), + 'string_invalid_utf8' => array("\xc3\x28"), + 'string_null_byte' => array('with'."\0".'null char'), + 'integer' => array(1), + 'float' => array(1.5), + 'object' => array(new ArrayObject(array('one', 2, 3.01))), + 'object_recursive' => array($obj), + 'true' => array(true), + // the following are considered FALSE in boolean context, but caches should still recognize their existence + 'null' => array(null), + 'false' => array(false), + 'array_empty' => array(array()), + 'string_zero' => array('0'), + 'integer_zero' => array(0), + 'float_zero' => array(0.0), + 'string_empty' => array(''), + ); + } + + public function testDeleteIsSuccessfulWhenKeyDoesNotExist() + { + $cache = $this->_getCacheDriver(); + + $cache->delete('key'); + $this->assertFalse($cache->contains('key')); + $this->assertTrue($cache->delete('key')); + } + + public function testDeleteAll() + { + $cache = $this->_getCacheDriver(); + + $this->assertTrue($cache->save('key1', 1)); + $this->assertTrue($cache->save('key2', 2)); + $this->assertTrue($cache->deleteAll()); + $this->assertFalse($cache->contains('key1')); + $this->assertFalse($cache->contains('key2')); + } + + /** + * @dataProvider provideCacheIds + */ + public function testCanHandleSpecialCacheIds($id) + { + $cache = $this->_getCacheDriver(); + + $this->assertTrue($cache->save($id, 'value')); + $this->assertTrue($cache->contains($id)); + $this->assertEquals('value', $cache->fetch($id)); + + $this->assertTrue($cache->delete($id)); + $this->assertFalse($cache->contains($id)); + $this->assertFalse($cache->fetch($id)); + } + + public function testNoCacheIdCollisions() + { + $cache = $this->_getCacheDriver(); + + $ids = $this->provideCacheIds(); + + // fill cache with each id having a different value + foreach ($ids as $index => $id) { + $cache->save($id[0], $index); + } + + // then check value of each cache id + foreach ($ids as $index => $id) { + $value = $cache->fetch($id[0]); + $this->assertNotFalse($value, sprintf('Failed to retrieve data for cache id "%s".', $id[0])); + if ($index !== $value) { + $this->fail(sprintf('Cache id "%s" collides with id "%s".', $id[0], $ids[$value][0])); + } + } + } + + /** + * Returns cache ids with special characters that should still work. + * + * For example, the characters :\/<>"*?| are not valid in Windows filenames. So they must be encoded properly. + * Each cache id should be considered different from the others. + * + * @return array + */ + public function provideCacheIds() + { + return array( + array(':'), + array('\\'), + array('/'), + array('<'), + array('>'), + array('"'), + array('*'), + array('?'), + array('|'), + array('['), + array(']'), + array('ä'), + array('a'), + array('é'), + array('e'), + array('.'), // directory traversal + array('..'), // directory traversal + array('-'), + array('_'), + array('$'), + array('%'), + array(' '), + array("\0"), + array(''), + array(str_repeat('a', 300)), // long key + array(str_repeat('a', 113)), + ); + } + + public function testLifetime() + { + $cache = $this->_getCacheDriver(); + $cache->save('expire', 'value', 1); + $this->assertTrue($cache->contains('expire'), 'Data should not be expired yet'); + // @TODO should more TTL-based tests pop up, so then we should mock the `time` API instead + sleep(2); + $this->assertFalse($cache->contains('expire'), 'Data should be expired'); + } + + public function testNoExpire() + { + $cache = $this->_getCacheDriver(); + $cache->save('noexpire', 'value', 0); + // @TODO should more TTL-based tests pop up, so then we should mock the `time` API instead + sleep(1); + $this->assertTrue($cache->contains('noexpire'), 'Data with lifetime of zero should not expire'); + } + + public function testLongLifetime() + { + $cache = $this->_getCacheDriver(); + $cache->save('longlifetime', 'value', 30 * 24 * 3600 + 1); + $this->assertTrue($cache->contains('longlifetime'), 'Data with lifetime > 30 days should be accepted'); + } + + public function testDeleteAllAndNamespaceVersioningBetweenCaches() + { + if ( ! $this->isSharedStorage()) { + $this->markTestSkipped('The cache storage needs to be shared.'); + } + + $cache1 = $this->_getCacheDriver(); + $cache2 = $this->_getCacheDriver(); + + $this->assertTrue($cache1->save('key1', 1)); + $this->assertTrue($cache2->save('key2', 2)); + + /* Both providers are initialized with the same namespace version, so + * they can see entries set by each other. + */ + $this->assertTrue($cache1->contains('key1')); + $this->assertTrue($cache1->contains('key2')); + $this->assertTrue($cache2->contains('key1')); + $this->assertTrue($cache2->contains('key2')); + + /* Deleting all entries through one provider will only increment the + * namespace version on that object (and in the cache itself, which new + * instances will use to initialize). The second provider will retain + * its original version and still see stale data. + */ + $this->assertTrue($cache1->deleteAll()); + $this->assertFalse($cache1->contains('key1')); + $this->assertFalse($cache1->contains('key2')); + $this->assertTrue($cache2->contains('key1')); + $this->assertTrue($cache2->contains('key2')); + + /* A new cache provider should not see the deleted entries, since its + * namespace version will be initialized. + */ + $cache3 = $this->_getCacheDriver(); + $this->assertFalse($cache3->contains('key1')); + $this->assertFalse($cache3->contains('key2')); + } + + public function testFlushAll() + { + $cache = $this->_getCacheDriver(); + + $this->assertTrue($cache->save('key1', 1)); + $this->assertTrue($cache->save('key2', 2)); + $this->assertTrue($cache->flushAll()); + $this->assertFalse($cache->contains('key1')); + $this->assertFalse($cache->contains('key2')); + } + + public function testFlushAllAndNamespaceVersioningBetweenCaches() + { + if ( ! $this->isSharedStorage()) { + $this->markTestSkipped('The cache storage needs to be shared.'); + } + + $cache1 = $this->_getCacheDriver(); + $cache2 = $this->_getCacheDriver(); + + /* Deleting all elements from the first provider should increment its + * namespace version before saving the first entry. + */ + $cache1->deleteAll(); + $this->assertTrue($cache1->save('key1', 1)); + + /* The second provider will be initialized with the same namespace + * version upon its first save operation. + */ + $this->assertTrue($cache2->save('key2', 2)); + + /* Both providers have the same namespace version and can see entries + * set by each other. + */ + $this->assertTrue($cache1->contains('key1')); + $this->assertTrue($cache1->contains('key2')); + $this->assertTrue($cache2->contains('key1')); + $this->assertTrue($cache2->contains('key2')); + + /* Flushing all entries through one cache will remove all entries from + * the cache but leave their namespace version as-is. + */ + $this->assertTrue($cache1->flushAll()); + $this->assertFalse($cache1->contains('key1')); + $this->assertFalse($cache1->contains('key2')); + $this->assertFalse($cache2->contains('key1')); + $this->assertFalse($cache2->contains('key2')); + + /* Inserting a new entry will use the same, incremented namespace + * version, and it will be visible to both providers. + */ + $this->assertTrue($cache1->save('key1', 1)); + $this->assertTrue($cache1->contains('key1')); + $this->assertTrue($cache2->contains('key1')); + + /* A new cache provider will be initialized with the original namespace + * version and not share any visibility with the first two providers. + */ + $cache3 = $this->_getCacheDriver(); + $this->assertFalse($cache3->contains('key1')); + $this->assertFalse($cache3->contains('key2')); + $this->assertTrue($cache3->save('key3', 3)); + $this->assertTrue($cache3->contains('key3')); + } + + public function testNamespace() + { + $cache = $this->_getCacheDriver(); + + $cache->setNamespace('ns1_'); + + $this->assertTrue($cache->save('key1', 1)); + $this->assertTrue($cache->contains('key1')); + + $cache->setNamespace('ns2_'); + + $this->assertFalse($cache->contains('key1')); + } + + public function testDeleteAllNamespace() + { + $cache = $this->_getCacheDriver(); + + $cache->setNamespace('ns1'); + $this->assertFalse($cache->contains('key1')); + $cache->save('key1', 'test'); + $this->assertTrue($cache->contains('key1')); + + $cache->setNamespace('ns2'); + $this->assertFalse($cache->contains('key1')); + $cache->save('key1', 'test'); + $this->assertTrue($cache->contains('key1')); + + $cache->setNamespace('ns1'); + $this->assertTrue($cache->contains('key1')); + $cache->deleteAll(); + $this->assertFalse($cache->contains('key1')); + + $cache->setNamespace('ns2'); + $this->assertTrue($cache->contains('key1')); + $cache->deleteAll(); + $this->assertFalse($cache->contains('key1')); + } + + /** + * @group DCOM-43 + */ + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertArrayHasKey(Cache::STATS_HITS, $stats); + $this->assertArrayHasKey(Cache::STATS_MISSES, $stats); + $this->assertArrayHasKey(Cache::STATS_UPTIME, $stats); + $this->assertArrayHasKey(Cache::STATS_MEMORY_USAGE, $stats); + $this->assertArrayHasKey(Cache::STATS_MEMORY_AVAILABLE, $stats); + } + + public function testSaveReturnsTrueWithAndWithoutTTlSet() + { + $cache = $this->_getCacheDriver(); + $cache->deleteAll(); + $this->assertTrue($cache->save('without_ttl', 'without_ttl')); + $this->assertTrue($cache->save('with_ttl', 'with_ttl', 3600)); + } + + /** + * Return whether multiple cache providers share the same storage. + * + * This is used for skipping certain tests for shared storage behavior. + * + * @return bool + */ + protected function isSharedStorage() + { + return true; + } + + /** + * @return \Doctrine\Common\Cache\CacheProvider + */ + abstract protected function _getCacheDriver(); +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ChainCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ChainCacheTest.php new file mode 100644 index 00000000..a3c013b8 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ChainCacheTest.php @@ -0,0 +1,99 @@ +markTestSkipped('The ChainCache test uses ArrayCache which does not implement TTL currently.'); + } + + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertInternalType('array', $stats); + } + + public function testOnlyFetchFirstOne() + { + $cache1 = new ArrayCache(); + $cache2 = $this->getMockForAbstractClass('Doctrine\Common\Cache\CacheProvider'); + + $cache2->expects($this->never())->method('doFetch'); + + $chainCache = new ChainCache(array($cache1, $cache2)); + $chainCache->save('id', 'bar'); + + $this->assertEquals('bar', $chainCache->fetch('id')); + } + + public function testFetchPropagateToFastestCache() + { + $cache1 = new ArrayCache(); + $cache2 = new ArrayCache(); + + $cache2->save('bar', 'value'); + + $chainCache = new ChainCache(array($cache1, $cache2)); + + $this->assertFalse($cache1->contains('bar')); + + $result = $chainCache->fetch('bar'); + + $this->assertEquals('value', $result); + $this->assertTrue($cache2->contains('bar')); + } + + public function testNamespaceIsPropagatedToAllProviders() + { + $cache1 = new ArrayCache(); + $cache2 = new ArrayCache(); + + $chainCache = new ChainCache(array($cache1, $cache2)); + $chainCache->setNamespace('bar'); + + $this->assertEquals('bar', $cache1->getNamespace()); + $this->assertEquals('bar', $cache2->getNamespace()); + } + + public function testDeleteToAllProviders() + { + $cache1 = $this->getMockForAbstractClass('Doctrine\Common\Cache\CacheProvider'); + $cache2 = $this->getMockForAbstractClass('Doctrine\Common\Cache\CacheProvider'); + + $cache1->expects($this->once())->method('doDelete'); + $cache2->expects($this->once())->method('doDelete'); + + $chainCache = new ChainCache(array($cache1, $cache2)); + $chainCache->delete('bar'); + } + + public function testFlushToAllProviders() + { + $cache1 = $this->getMockForAbstractClass('Doctrine\Common\Cache\CacheProvider'); + $cache2 = $this->getMockForAbstractClass('Doctrine\Common\Cache\CacheProvider'); + + $cache1->expects($this->once())->method('doFlush'); + $cache2->expects($this->once())->method('doFlush'); + + $chainCache = new ChainCache(array($cache1, $cache2)); + $chainCache->flushAll(); + } + + protected function isSharedStorage() + { + return false; + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CouchbaseCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CouchbaseCacheTest.php new file mode 100644 index 00000000..f42b3a7f --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CouchbaseCacheTest.php @@ -0,0 +1,30 @@ +couchbase = new Couchbase('127.0.0.1', 'Administrator', 'password', 'default'); + } catch(Exception $ex) { + $this->markTestSkipped('Could not instantiate the Couchbase cache because of: ' . $ex); + } + } + + protected function _getCacheDriver() + { + $driver = new CouchbaseCache(); + $driver->setCouchbase($this->couchbase); + return $driver; + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FileCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FileCacheTest.php new file mode 100644 index 00000000..f27d5428 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FileCacheTest.php @@ -0,0 +1,268 @@ +driver = $this->getMock( + 'Doctrine\Common\Cache\FileCache', + array('doFetch', 'doContains', 'doSave'), + array(), '', false + ); + } + + public function testFilenameShouldCreateThePathWithOneSubDirectory() + { + $cache = $this->driver; + $method = new \ReflectionMethod($cache, 'getFilename'); + $key = 'item-key'; + $expectedDir = array( + '84', + ); + $expectedDir = implode(DIRECTORY_SEPARATOR, $expectedDir); + + $method->setAccessible(true); + + $path = $method->invoke($cache, $key); + $dirname = pathinfo($path, PATHINFO_DIRNAME); + + $this->assertEquals(DIRECTORY_SEPARATOR . $expectedDir, $dirname); + } + + public function testFileExtensionCorrectlyEscaped() + { + $driver1 = $this->getMock( + 'Doctrine\Common\Cache\FileCache', + array('doFetch', 'doContains', 'doSave'), + array(__DIR__, '.*') + ); + $driver2 = $this->getMock( + 'Doctrine\Common\Cache\FileCache', + array('doFetch', 'doContains', 'doSave'), + array(__DIR__, '.php') + ); + + $doGetStats = new \ReflectionMethod($driver1, 'doGetStats'); + + $doGetStats->setAccessible(true); + + $stats1 = $doGetStats->invoke($driver1); + $stats2 = $doGetStats->invoke($driver2); + + $this->assertSame(0, $stats1[Cache::STATS_MEMORY_USAGE]); + $this->assertGreaterThan(0, $stats2[Cache::STATS_MEMORY_USAGE]); + } + + /** + * @group DCOM-266 + */ + public function testFileExtensionSlashCorrectlyEscaped() + { + $driver = $this->getMock( + 'Doctrine\Common\Cache\FileCache', + array('doFetch', 'doContains', 'doSave'), + array(__DIR__ . '/../', DIRECTORY_SEPARATOR . basename(__FILE__)) + ); + + $doGetStats = new \ReflectionMethod($driver, 'doGetStats'); + + $doGetStats->setAccessible(true); + + $stats = $doGetStats->invoke($driver); + + $this->assertGreaterThan(0, $stats[Cache::STATS_MEMORY_USAGE]); + } + + public function testNonIntUmaskThrowsInvalidArgumentException() + { + $this->setExpectedException('InvalidArgumentException'); + + $this->getMock( + 'Doctrine\Common\Cache\FileCache', + array('doFetch', 'doContains', 'doSave'), + array('', '', 'invalid') + ); + } + + public function testGetDirectoryReturnsRealpathDirectoryString() + { + $directory = __DIR__ . '/../'; + $driver = $this->getMock( + 'Doctrine\Common\Cache\FileCache', + array('doFetch', 'doContains', 'doSave'), + array($directory) + ); + + $doGetDirectory = new \ReflectionMethod($driver, 'getDirectory'); + + $actualDirectory = $doGetDirectory->invoke($driver); + $expectedDirectory = realpath($directory); + + $this->assertEquals($expectedDirectory, $actualDirectory); + } + + public function testGetExtensionReturnsExtensionString() + { + $directory = __DIR__ . '/../'; + $extension = DIRECTORY_SEPARATOR . basename(__FILE__); + $driver = $this->getMock( + 'Doctrine\Common\Cache\FileCache', + array('doFetch', 'doContains', 'doSave'), + array($directory, $extension) + ); + + $doGetExtension = new \ReflectionMethod($driver, 'getExtension'); + + $actualExtension = $doGetExtension->invoke($driver); + + $this->assertEquals($extension, $actualExtension); + } + + const WIN_MAX_PATH_LEN = 258; + + public static function getBasePathForWindowsPathLengthTests($pathLength) + { + // Not using __DIR__ because it can get screwed up when xdebug debugger is attached. + $basePath = realpath(sys_get_temp_dir()) . '/' . uniqid('doctrine-cache', true); + + /** @noinspection MkdirRaceConditionInspection */ + @mkdir($basePath); + + $basePath = realpath($basePath); + + // Test whether the desired path length is odd or even. + $desiredPathLengthIsOdd = ($pathLength % 2) == 1; + + // If the cache key is not too long, the filecache codepath will add + // a slash and bin2hex($key). The length of the added portion will be an odd number. + // len(desired) = len(base path) + len(slash . bin2hex($key)) + // odd = even + odd + // even = odd + odd + $basePathLengthShouldBeOdd = !$desiredPathLengthIsOdd; + + $basePathLengthIsOdd = (strlen($basePath) % 2) == 1; + + // If the base path needs to be odd or even where it is not, we add an odd number of + // characters as a pad. In this case, we're adding '\aa' (or '/aa' depending on platform) + // This is all to make it so that the key we're testing would result in + // a path that is exactly the length we want to test IF the path length limit + // were not in place in FileCache. + if ($basePathLengthIsOdd != $basePathLengthShouldBeOdd) { + $basePath .= DIRECTORY_SEPARATOR . "aa"; + } + + return $basePath; + } + + /** + * @param int $length + * @param string $basePath + * + * @return array + */ + public static function getKeyAndPathFittingLength($length, $basePath) + { + $baseDirLength = strlen($basePath); + $extensionLength = strlen('.doctrine.cache'); + $directoryLength = strlen(DIRECTORY_SEPARATOR . 'aa' . DIRECTORY_SEPARATOR); + $keyLength = $length - ($baseDirLength + $extensionLength + $directoryLength); // - 1 because of slash + + $key = str_repeat('a', floor($keyLength / 2)); + + $keyHash = hash('sha256', $key); + + $keyPath = $basePath + . DIRECTORY_SEPARATOR + . substr($keyHash, 0, 2) + . DIRECTORY_SEPARATOR + . bin2hex($key) + . '.doctrine.cache'; + + $hashedKeyPath = $basePath + . DIRECTORY_SEPARATOR + . substr($keyHash, 0, 2) + . DIRECTORY_SEPARATOR + . '_' . $keyHash + . '.doctrine.cache'; + + return array($key, $keyPath, $hashedKeyPath); + } + + public function getPathLengthsToTest() + { + // Windows officially supports 260 bytes including null terminator + // 259 characters is too large due to PHP bug (https://bugs.php.net/bug.php?id=70943) + // 260 characters is too large - null terminator is included in allowable length + return array( + array(257, false), + array(258, false), + array(259, true), + array(260, true) + ); + } + + /** + * @runInSeparateProcess + * @dataProvider getPathLengthsToTest + * + * @covers \Doctrine\Common\Cache\FileCache::getFilename + * + * @param int $length + * @param bool $pathShouldBeHashed + */ + public function testWindowsPathLengthLimitationsAreCorrectlyRespected($length, $pathShouldBeHashed) + { + if (! defined('PHP_WINDOWS_VERSION_BUILD')) { + define('PHP_WINDOWS_VERSION_BUILD', 'Yes, this is the "usual suspect", with the usual limitations'); + } + + $basePath = self::getBasePathForWindowsPathLengthTests($length); + + $fileCache = $this->getMockForAbstractClass( + 'Doctrine\Common\Cache\FileCache', + array($basePath, '.doctrine.cache') + ); + + list($key, $keyPath, $hashedKeyPath) = self::getKeyAndPathFittingLength($length, $basePath); + + $getFileName = new \ReflectionMethod($fileCache, 'getFilename'); + + $getFileName->setAccessible(true); + + $this->assertEquals( + $length, + strlen($keyPath), + sprintf('Path expected to be %d characters long is %d characters long', $length, strlen($keyPath)) + ); + + if ($pathShouldBeHashed) { + $keyPath = $hashedKeyPath; + } + + if ($pathShouldBeHashed) { + $this->assertSame( + $hashedKeyPath, + $getFileName->invoke($fileCache, $key), + 'Keys should be hashed correctly if they are over the limit.' + ); + } else { + $this->assertSame( + $keyPath, + $getFileName->invoke($fileCache, $key), + 'Keys below limit of the allowed length are used directly, unhashed' + ); + } + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FilesystemCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FilesystemCacheTest.php new file mode 100644 index 00000000..9e7c9e81 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FilesystemCacheTest.php @@ -0,0 +1,61 @@ +_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNull($stats[Cache::STATS_HITS]); + $this->assertNull($stats[Cache::STATS_MISSES]); + $this->assertNull($stats[Cache::STATS_UPTIME]); + $this->assertEquals(0, $stats[Cache::STATS_MEMORY_USAGE]); + $this->assertGreaterThan(0, $stats[Cache::STATS_MEMORY_AVAILABLE]); + } + + public function testCacheInSharedDirectoryIsPerExtension() + { + $cache1 = new FilesystemCache($this->directory, '.foo'); + $cache2 = new FilesystemCache($this->directory, '.bar'); + + $this->assertTrue($cache1->save('key1', 11)); + $this->assertTrue($cache1->save('key2', 12)); + + $this->assertTrue($cache2->save('key1', 21)); + $this->assertTrue($cache2->save('key2', 22)); + + $this->assertSame(11, $cache1->fetch('key1'), 'Cache value must not be influenced by a different cache in the same directory but different extension'); + $this->assertSame(12, $cache1->fetch('key2')); + $this->assertTrue($cache1->flushAll()); + $this->assertFalse($cache1->fetch('key1'), 'flushAll() must delete all items with the current extension'); + $this->assertFalse($cache1->fetch('key2')); + + $this->assertSame(21, $cache2->fetch('key1'), 'flushAll() must not remove items with a different extension in a shared directory'); + $this->assertSame(22, $cache2->fetch('key2')); + } + + public function testFlushAllWithNoExtension() + { + $cache = new FilesystemCache($this->directory, ''); + + $this->assertTrue($cache->save('key1', 1)); + $this->assertTrue($cache->save('key2', 2)); + $this->assertTrue($cache->flushAll()); + $this->assertFalse($cache->contains('key1')); + $this->assertFalse($cache->contains('key2')); + } + + protected function _getCacheDriver() + { + return new FilesystemCache($this->directory); + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcacheCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcacheCacheTest.php new file mode 100644 index 00000000..74853b84 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcacheCacheTest.php @@ -0,0 +1,59 @@ +memcache = new Memcache(); + + if (@$this->memcache->connect('localhost', 11211) === false) { + unset($this->memcache); + $this->markTestSkipped('Cannot connect to Memcache.'); + } + } + + protected function tearDown() + { + if ($this->memcache instanceof Memcache) { + $this->memcache->flush(); + } + } + + /** + * {@inheritdoc} + * + * Memcache does not support " " and null byte as key so we remove them from the tests. + */ + public function provideCacheIds() + { + $ids = parent::provideCacheIds(); + unset($ids[21], $ids[22]); + + return $ids; + } + + public function testGetMemcacheReturnsInstanceOfMemcache() + { + $this->assertInstanceOf('Memcache', $this->_getCacheDriver()->getMemcache()); + } + + /** + * {@inheritDoc} + */ + protected function _getCacheDriver() + { + $driver = new MemcacheCache(); + $driver->setMemcache($this->memcache); + return $driver; + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcachedCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcachedCacheTest.php new file mode 100644 index 00000000..fbcf5c38 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcachedCacheTest.php @@ -0,0 +1,61 @@ +memcached = new Memcached(); + $this->memcached->setOption(Memcached::OPT_COMPRESSION, false); + $this->memcached->addServer('127.0.0.1', 11211); + + if (@fsockopen('127.0.0.1', 11211) === false) { + unset($this->memcached); + $this->markTestSkipped('Cannot connect to Memcached.'); + } + } + + protected function tearDown() + { + if ($this->memcached instanceof Memcached) { + $this->memcached->flush(); + } + } + + /** + * {@inheritdoc} + * + * Memcached does not support " ", null byte and very long keys so we remove them from the tests. + */ + public function provideCacheIds() + { + $ids = parent::provideCacheIds(); + unset($ids[21], $ids[22], $ids[24]); + + return $ids; + } + + public function testGetMemcachedReturnsInstanceOfMemcached() + { + $this->assertInstanceOf('Memcached', $this->_getCacheDriver()->getMemcached()); + } + + /** + * {@inheritDoc} + */ + protected function _getCacheDriver() + { + $driver = new MemcachedCache(); + $driver->setMemcached($this->memcached); + return $driver; + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MongoDBCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MongoDBCacheTest.php new file mode 100644 index 00000000..fa7cfacd --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MongoDBCacheTest.php @@ -0,0 +1,68 @@ +=')) { + $this->markTestSkipped('Mongo >= 1.3.0 is required.'); + } + + $mongo = new MongoClient(); + $this->collection = $mongo->selectCollection('doctrine_common_cache', 'test'); + } + + protected function tearDown() + { + if ($this->collection instanceof MongoCollection) { + $this->collection->drop(); + } + } + + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNull($stats[Cache::STATS_HITS]); + $this->assertNull($stats[Cache::STATS_MISSES]); + $this->assertGreaterThan(0, $stats[Cache::STATS_UPTIME]); + $this->assertEquals(0, $stats[Cache::STATS_MEMORY_USAGE]); + $this->assertNull($stats[Cache::STATS_MEMORY_AVAILABLE]); + } + + /** + * @group 108 + */ + public function testMongoCursorExceptionsDoNotBubbleUp() + { + /* @var $collection \MongoCollection|\PHPUnit_Framework_MockObject_MockObject */ + $collection = $this->getMock('MongoCollection', array(), array(), '', false); + + $collection->expects(self::once())->method('update')->willThrowException(new \MongoCursorException()); + + $cache = new MongoDBCache($collection); + + self::assertFalse($cache->save('foo', 'bar')); + } + + protected function _getCacheDriver() + { + return new MongoDBCache($this->collection); + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/PhpFileCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/PhpFileCacheTest.php new file mode 100644 index 00000000..d1851cb3 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/PhpFileCacheTest.php @@ -0,0 +1,98 @@ +_getCacheDriver(); + + // Test save + $cache->save('test_set_state', new SetStateClass(array(1,2,3))); + + //Test __set_state call + $this->assertCount(0, SetStateClass::$values); + + // Test fetch + $value = $cache->fetch('test_set_state'); + $this->assertInstanceOf('Doctrine\Tests\Common\Cache\SetStateClass', $value); + $this->assertEquals(array(1,2,3), $value->getValue()); + + //Test __set_state call + $this->assertCount(1, SetStateClass::$values); + + // Test contains + $this->assertTrue($cache->contains('test_set_state')); + } + + public function testNotImplementsSetState() + { + $cache = $this->_getCacheDriver(); + + $this->setExpectedException('InvalidArgumentException'); + $cache->save('test_not_set_state', new NotSetStateClass(array(1,2,3))); + } + + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNull($stats[Cache::STATS_HITS]); + $this->assertNull($stats[Cache::STATS_MISSES]); + $this->assertNull($stats[Cache::STATS_UPTIME]); + $this->assertEquals(0, $stats[Cache::STATS_MEMORY_USAGE]); + $this->assertGreaterThan(0, $stats[Cache::STATS_MEMORY_AVAILABLE]); + } + + protected function _getCacheDriver() + { + return new PhpFileCache($this->directory); + } +} + +class NotSetStateClass +{ + private $value; + + public function __construct($value) + { + $this->value = $value; + } + + public function getValue() + { + return $this->value; + } +} + +class SetStateClass extends NotSetStateClass +{ + public static $values = array(); + + public static function __set_state($data) + { + self::$values = $data; + return new self($data['value']); + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/PredisCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/PredisCacheTest.php new file mode 100644 index 00000000..e20839dc --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/PredisCacheTest.php @@ -0,0 +1,87 @@ +markTestSkipped('Predis\Client is missing. Make sure to "composer install" to have all dev dependencies.'); + } + + $this->client = new Client(); + + try { + $this->client->connect(); + } catch (ConnectionException $e) { + $this->markTestSkipped('Cannot connect to Redis because of: ' . $e); + } + } + + public function testHitMissesStatsAreProvided() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNotNull($stats[Cache::STATS_HITS]); + $this->assertNotNull($stats[Cache::STATS_MISSES]); + } + + /** + * @return PredisCache + */ + protected function _getCacheDriver() + { + return new PredisCache($this->client); + } + + /** + * {@inheritDoc} + * + * @dataProvider provideDataToCache + */ + public function testSetContainsFetchDelete($value) + { + if (array() === $value) { + $this->markTestIncomplete( + 'Predis currently doesn\'t support saving empty array values. ' + . 'See https://github.com/nrk/predis/issues/241' + ); + } + + parent::testSetContainsFetchDelete($value); + } + + /** + * {@inheritDoc} + * + * @dataProvider provideDataToCache + */ + public function testUpdateExistingEntry($value) + { + if (array() === $value) { + $this->markTestIncomplete( + 'Predis currently doesn\'t support saving empty array values. ' + . 'See https://github.com/nrk/predis/issues/241' + ); + } + + parent::testUpdateExistingEntry($value); + } + + public function testAllowsGenericPredisClient() + { + /* @var $predisClient \Predis\ClientInterface */ + $predisClient = $this->getMock('Predis\\ClientInterface'); + + $this->assertInstanceOf('Doctrine\\Common\\Cache\\PredisCache', new PredisCache($predisClient)); + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RedisCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RedisCacheTest.php new file mode 100644 index 00000000..5fb6a11d --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RedisCacheTest.php @@ -0,0 +1,59 @@ +_redis = new \Redis(); + $ok = @$this->_redis->connect('127.0.0.1'); + if (!$ok) { + $this->markTestSkipped('Cannot connect to Redis.'); + } + } + + public function testHitMissesStatsAreProvided() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNotNull($stats[Cache::STATS_HITS]); + $this->assertNotNull($stats[Cache::STATS_MISSES]); + } + + public function testGetRedisReturnsInstanceOfRedis() + { + $this->assertInstanceOf('Redis', $this->_getCacheDriver()->getRedis()); + } + + public function testSerializerOptionWithOutIgbinaryExtension() + { + if (defined('Redis::SERIALIZER_IGBINARY') && extension_loaded('igbinary')) { + $this->markTestSkipped('Extension igbinary is loaded.'); + } + + $this->assertEquals( + \Redis::SERIALIZER_PHP, + $this->_getCacheDriver()->getRedis()->getOption(\Redis::OPT_SERIALIZER) + ); + } + + /** + * {@inheritDoc} + */ + protected function _getCacheDriver() + { + $driver = new RedisCache(); + $driver->setRedis($this->_redis); + return $driver; + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RiakCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RiakCacheTest.php new file mode 100644 index 00000000..e7d5f3dd --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RiakCacheTest.php @@ -0,0 +1,58 @@ +connection = new Connection('127.0.0.1', 8087); + $this->bucket = new Bucket($this->connection, 'test'); + } catch (Exception\RiakException $e) { + $this->markTestSkipped('Cannot connect to Riak.'); + } + } + + /** + * {@inheritdoc} + */ + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNull($stats); + } + + /** + * Retrieve RiakCache instance. + * + * @return \Doctrine\Common\Cache\RiakCache + */ + protected function _getCacheDriver() + { + return new RiakCache($this->bucket); + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/SQLite3CacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/SQLite3CacheTest.php new file mode 100644 index 00000000..f0c5b0b5 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/SQLite3CacheTest.php @@ -0,0 +1,42 @@ +file = tempnam(null, 'doctrine-cache-test-'); + unlink($this->file); + $this->sqlite = new SQLite3($this->file); + } + + protected function tearDown() + { + $this->sqlite = null; // DB must be closed before + unlink($this->file); + } + + public function testGetStats() + { + $this->assertNull($this->_getCacheDriver()->getStats()); + } + + /** + * {@inheritDoc} + */ + protected function _getCacheDriver() + { + return new SQLite3Cache($this->sqlite, 'test_table'); + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/VoidCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/VoidCacheTest.php new file mode 100644 index 00000000..8ab7e5b4 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/VoidCacheTest.php @@ -0,0 +1,58 @@ +assertFalse($cache->contains('foo')); + $this->assertFalse($cache->contains('bar')); + } + + public function testShouldAlwaysReturnFalseOnFetch() + { + $cache = new VoidCache(); + + $this->assertFalse($cache->fetch('foo')); + $this->assertFalse($cache->fetch('bar')); + } + + public function testShouldAlwaysReturnTrueOnSaveButNotStoreAnything() + { + $cache = new VoidCache(); + + $this->assertTrue($cache->save('foo', 'fooVal')); + + $this->assertFalse($cache->contains('foo')); + $this->assertFalse($cache->fetch('foo')); + } + + public function testShouldAlwaysReturnTrueOnDelete() + { + $cache = new VoidCache(); + + $this->assertTrue($cache->delete('foo')); + } + + public function testShouldAlwaysReturnNullOnGetStatus() + { + $cache = new VoidCache(); + + $this->assertNull($cache->getStats()); + } + + public function testShouldAlwaysReturnTrueOnFlush() + { + $cache = new VoidCache(); + + $this->assertTrue($cache->flushAll()); + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/WinCacheCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/WinCacheCacheTest.php new file mode 100644 index 00000000..1dded424 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/WinCacheCacheTest.php @@ -0,0 +1,16 @@ +markTestSkipped('Zend Data Cache only works in apache2handler SAPI.'); + } + } + + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNull($stats); + } + + protected function _getCacheDriver() + { + return new ZendDataCache(); + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/DoctrineTestCase.php b/vendor/doctrine/cache/tests/Doctrine/Tests/DoctrineTestCase.php new file mode 100644 index 00000000..e8323d29 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/DoctrineTestCase.php @@ -0,0 +1,10 @@ + + + + + + + + + + + + + + ../Doctrine/ + + + + + + ../../lib/Doctrine/ + + + + + + performance + + + diff --git a/vendor/doctrine/collections/CONTRIBUTING.md b/vendor/doctrine/collections/CONTRIBUTING.md new file mode 100644 index 00000000..407a6652 --- /dev/null +++ b/vendor/doctrine/collections/CONTRIBUTING.md @@ -0,0 +1,67 @@ +# Contribute to Doctrine + +Thank you for contributing to Doctrine! + +Before we can merge your Pull-Request here are some guidelines that you need to follow. +These guidelines exist not to annoy you, but to keep the code base clean, +unified and future proof. + +## We only accept PRs to "master" + +Our branching strategy is "everything to master first", even +bugfixes and we then merge them into the stable branches. You should only +open pull requests against the master branch. Otherwise we cannot accept the PR. + +There is one exception to the rule, when we merged a bug into some stable branches +we do occasionally accept pull requests that merge the same bug fix into earlier +branches. + +## Coding Standard + +We use [doctrine coding standard](https://github.com/doctrine/coding-standard) which is PSR-1 and PSR-2: + +* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md +* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md + +with some exceptions/differences: + +* Keep the nesting of control structures per method as small as possible +* Align equals (=) signs +* Add spaces between assignment, control and return statements +* Prefer early exit over nesting conditions +* Add spaces around a negation if condition ``if ( ! $cond)`` +* Add legal information at the beginning of each source file +* Add ``@author`` [phpDoc](https://www.phpdoc.org/docs/latest/references/phpdoc/tags/author.html) comment at DockBlock of class/interface/trait that you create. + +## Unit-Tests + +Please try to add a test for your pull-request. + +* If you want to contribute new functionality add unit- or functional tests + depending on the scope of the feature. + +You can run the unit-tests by calling ``vendor/bin/phpunit`` from the root of the project. +It will run all the project tests. + +In order to do that, you will need a fresh copy of doctrine/collections, and you +will have to run a composer installation in the project: + +```sh +git clone git@github.com:doctrine/collections.git +cd collections +curl -sS https://getcomposer.org/installer | php -- +./composer.phar install +``` + +## Travis + +We automatically run your pull request through [Travis CI](https://www.travis-ci.org) +against supported PHP versions. If you break the tests, we cannot merge your code, +so please make sure that your code is working before opening up a Pull-Request. + +## Getting merged + +Please allow us time to review your pull requests. We will give our best to review +everything as fast as possible, but cannot always live up to our own expectations. + +Thank you very much again for your contribution! diff --git a/vendor/doctrine/collections/LICENSE b/vendor/doctrine/collections/LICENSE new file mode 100644 index 00000000..5e781fce --- /dev/null +++ b/vendor/doctrine/collections/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2013 Doctrine Project + +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/vendor/doctrine/collections/README.md b/vendor/doctrine/collections/README.md new file mode 100644 index 00000000..81e06d0c --- /dev/null +++ b/vendor/doctrine/collections/README.md @@ -0,0 +1,27 @@ +# Doctrine Collections + +[![Build Status](https://travis-ci.org/doctrine/collections.svg?branch=master)](https://travis-ci.org/doctrine/collections) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/doctrine/collections/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/doctrine/collections/?branch=master) +[![Code Coverage](https://scrutinizer-ci.com/g/doctrine/collections/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/doctrine/collections/?branch=master) + +Collections Abstraction library + +## Changelog + +### v1.3.0 + +* [Explicit casting of first and max results in criteria API](https://github.com/doctrine/collections/pull/26) +* [Keep keys when using `ArrayCollection#matching()` with sorting](https://github.com/doctrine/collections/pull/49) +* [Made `AbstractLazyCollection#$initialized` protected for extensibility](https://github.com/doctrine/collections/pull/52) + +### v1.2.0 + +* Add a new ``AbstractLazyCollection`` + +### v1.1.0 + +* Deprecated ``Comparison::IS``, because it's only there for SQL semantics. + These are fixed in the ORM instead. +* Add ``Comparison::CONTAINS`` to perform partial string matches: + + $criteria->andWhere($criteria->expr()->contains('property', 'Foo')); diff --git a/vendor/doctrine/collections/composer.json b/vendor/doctrine/collections/composer.json new file mode 100644 index 00000000..385fa93d --- /dev/null +++ b/vendor/doctrine/collections/composer.json @@ -0,0 +1,35 @@ +{ + "name": "doctrine/collections", + "type": "library", + "description": "Collections Abstraction library", + "keywords": ["collections", "array", "iterator"], + "homepage": "http://www.doctrine-project.org", + "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"} + ], + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7", + "doctrine/coding-standard": "~0.1@dev" + }, + "autoload": { + "psr-0": { "Doctrine\\Common\\Collections\\": "lib/" } + }, + "autoload-dev": { + "psr-4": { + "Doctrine\\Tests\\": "tests/Doctrine/Tests" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/AbstractLazyCollection.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/AbstractLazyCollection.php new file mode 100644 index 00000000..b907f8b0 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/AbstractLazyCollection.php @@ -0,0 +1,343 @@ +. + */ + +namespace Doctrine\Common\Collections; + +use Closure; + +/** + * Lazy collection that is backed by a concrete collection + * + * @author Michaël Gallego + * @since 1.2 + */ +abstract class AbstractLazyCollection implements Collection +{ + /** + * The backed collection to use + * + * @var Collection + */ + protected $collection; + + /** + * @var boolean + */ + protected $initialized = false; + + /** + * {@inheritDoc} + */ + public function count() + { + $this->initialize(); + return $this->collection->count(); + } + + /** + * {@inheritDoc} + */ + public function add($element) + { + $this->initialize(); + return $this->collection->add($element); + } + + /** + * {@inheritDoc} + */ + public function clear() + { + $this->initialize(); + $this->collection->clear(); + } + + /** + * {@inheritDoc} + */ + public function contains($element) + { + $this->initialize(); + return $this->collection->contains($element); + } + + /** + * {@inheritDoc} + */ + public function isEmpty() + { + $this->initialize(); + return $this->collection->isEmpty(); + } + + /** + * {@inheritDoc} + */ + public function remove($key) + { + $this->initialize(); + return $this->collection->remove($key); + } + + /** + * {@inheritDoc} + */ + public function removeElement($element) + { + $this->initialize(); + return $this->collection->removeElement($element); + } + + /** + * {@inheritDoc} + */ + public function containsKey($key) + { + $this->initialize(); + return $this->collection->containsKey($key); + } + + /** + * {@inheritDoc} + */ + public function get($key) + { + $this->initialize(); + return $this->collection->get($key); + } + + /** + * {@inheritDoc} + */ + public function getKeys() + { + $this->initialize(); + return $this->collection->getKeys(); + } + + /** + * {@inheritDoc} + */ + public function getValues() + { + $this->initialize(); + return $this->collection->getValues(); + } + + /** + * {@inheritDoc} + */ + public function set($key, $value) + { + $this->initialize(); + $this->collection->set($key, $value); + } + + /** + * {@inheritDoc} + */ + public function toArray() + { + $this->initialize(); + return $this->collection->toArray(); + } + + /** + * {@inheritDoc} + */ + public function first() + { + $this->initialize(); + return $this->collection->first(); + } + + /** + * {@inheritDoc} + */ + public function last() + { + $this->initialize(); + return $this->collection->last(); + } + + /** + * {@inheritDoc} + */ + public function key() + { + $this->initialize(); + return $this->collection->key(); + } + + /** + * {@inheritDoc} + */ + public function current() + { + $this->initialize(); + return $this->collection->current(); + } + + /** + * {@inheritDoc} + */ + public function next() + { + $this->initialize(); + return $this->collection->next(); + } + + /** + * {@inheritDoc} + */ + public function exists(Closure $p) + { + $this->initialize(); + return $this->collection->exists($p); + } + + /** + * {@inheritDoc} + */ + public function filter(Closure $p) + { + $this->initialize(); + return $this->collection->filter($p); + } + + /** + * {@inheritDoc} + */ + public function forAll(Closure $p) + { + $this->initialize(); + return $this->collection->forAll($p); + } + + /** + * {@inheritDoc} + */ + public function map(Closure $func) + { + $this->initialize(); + return $this->collection->map($func); + } + + /** + * {@inheritDoc} + */ + public function partition(Closure $p) + { + $this->initialize(); + return $this->collection->partition($p); + } + + /** + * {@inheritDoc} + */ + public function indexOf($element) + { + $this->initialize(); + return $this->collection->indexOf($element); + } + + /** + * {@inheritDoc} + */ + public function slice($offset, $length = null) + { + $this->initialize(); + return $this->collection->slice($offset, $length); + } + + /** + * {@inheritDoc} + */ + public function getIterator() + { + $this->initialize(); + return $this->collection->getIterator(); + } + + /** + * {@inheritDoc} + */ + public function offsetExists($offset) + { + $this->initialize(); + return $this->collection->offsetExists($offset); + } + + /** + * {@inheritDoc} + */ + public function offsetGet($offset) + { + $this->initialize(); + return $this->collection->offsetGet($offset); + } + + /** + * {@inheritDoc} + */ + public function offsetSet($offset, $value) + { + $this->initialize(); + $this->collection->offsetSet($offset, $value); + } + + /** + * {@inheritDoc} + */ + public function offsetUnset($offset) + { + $this->initialize(); + $this->collection->offsetUnset($offset); + } + + /** + * Is the lazy collection already initialized? + * + * @return bool + */ + public function isInitialized() + { + return $this->initialized; + } + + /** + * Initialize the collection + * + * @return void + */ + protected function initialize() + { + if ( ! $this->initialized) { + $this->doInitialize(); + $this->initialized = true; + } + } + + /** + * Do the initialization logic + * + * @return void + */ + abstract protected function doInitialize(); +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php new file mode 100644 index 00000000..7ddd068e --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php @@ -0,0 +1,408 @@ +. + */ + +namespace Doctrine\Common\Collections; + +use ArrayIterator; +use Closure; +use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor; + +/** + * An ArrayCollection is a Collection implementation that wraps a regular PHP array. + * + * Warning: Using (un-)serialize() on a collection is not a supported use-case + * and may break when we change the internals in the future. If you need to + * serialize a collection use {@link toArray()} and reconstruct the collection + * manually. + * + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class ArrayCollection implements Collection, Selectable +{ + /** + * An array containing the entries of this collection. + * + * @var array + */ + private $elements; + + /** + * Initializes a new ArrayCollection. + * + * @param array $elements + */ + public function __construct(array $elements = array()) + { + $this->elements = $elements; + } + + /** + * Creates a new instance from the specified elements. + * + * This method is provided for derived classes to specify how a new + * instance should be created when constructor semantics have changed. + * + * @param array $elements Elements. + * + * @return static + */ + protected function createFrom(array $elements) + { + return new static($elements); + } + + /** + * {@inheritDoc} + */ + public function toArray() + { + return $this->elements; + } + + /** + * {@inheritDoc} + */ + public function first() + { + return reset($this->elements); + } + + /** + * {@inheritDoc} + */ + public function last() + { + return end($this->elements); + } + + /** + * {@inheritDoc} + */ + public function key() + { + return key($this->elements); + } + + /** + * {@inheritDoc} + */ + public function next() + { + return next($this->elements); + } + + /** + * {@inheritDoc} + */ + public function current() + { + return current($this->elements); + } + + /** + * {@inheritDoc} + */ + public function remove($key) + { + if ( ! isset($this->elements[$key]) && ! array_key_exists($key, $this->elements)) { + return null; + } + + $removed = $this->elements[$key]; + unset($this->elements[$key]); + + return $removed; + } + + /** + * {@inheritDoc} + */ + public function removeElement($element) + { + $key = array_search($element, $this->elements, true); + + if ($key === false) { + return false; + } + + unset($this->elements[$key]); + + return true; + } + + /** + * Required by interface ArrayAccess. + * + * {@inheritDoc} + */ + public function offsetExists($offset) + { + return $this->containsKey($offset); + } + + /** + * Required by interface ArrayAccess. + * + * {@inheritDoc} + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * Required by interface ArrayAccess. + * + * {@inheritDoc} + */ + public function offsetSet($offset, $value) + { + if ( ! isset($offset)) { + return $this->add($value); + } + + $this->set($offset, $value); + } + + /** + * Required by interface ArrayAccess. + * + * {@inheritDoc} + */ + public function offsetUnset($offset) + { + return $this->remove($offset); + } + + /** + * {@inheritDoc} + */ + public function containsKey($key) + { + return isset($this->elements[$key]) || array_key_exists($key, $this->elements); + } + + /** + * {@inheritDoc} + */ + public function contains($element) + { + return in_array($element, $this->elements, true); + } + + /** + * {@inheritDoc} + */ + public function exists(Closure $p) + { + foreach ($this->elements as $key => $element) { + if ($p($key, $element)) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function indexOf($element) + { + return array_search($element, $this->elements, true); + } + + /** + * {@inheritDoc} + */ + public function get($key) + { + return isset($this->elements[$key]) ? $this->elements[$key] : null; + } + + /** + * {@inheritDoc} + */ + public function getKeys() + { + return array_keys($this->elements); + } + + /** + * {@inheritDoc} + */ + public function getValues() + { + return array_values($this->elements); + } + + /** + * {@inheritDoc} + */ + public function count() + { + return count($this->elements); + } + + /** + * {@inheritDoc} + */ + public function set($key, $value) + { + $this->elements[$key] = $value; + } + + /** + * {@inheritDoc} + */ + public function add($element) + { + $this->elements[] = $element; + + return true; + } + + /** + * {@inheritDoc} + */ + public function isEmpty() + { + return empty($this->elements); + } + + /** + * Required by interface IteratorAggregate. + * + * {@inheritDoc} + */ + public function getIterator() + { + return new ArrayIterator($this->elements); + } + + /** + * {@inheritDoc} + */ + public function map(Closure $func) + { + return $this->createFrom(array_map($func, $this->elements)); + } + + /** + * {@inheritDoc} + */ + public function filter(Closure $p) + { + return $this->createFrom(array_filter($this->elements, $p)); + } + + /** + * {@inheritDoc} + */ + public function forAll(Closure $p) + { + foreach ($this->elements as $key => $element) { + if ( ! $p($key, $element)) { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function partition(Closure $p) + { + $matches = $noMatches = array(); + + foreach ($this->elements as $key => $element) { + if ($p($key, $element)) { + $matches[$key] = $element; + } else { + $noMatches[$key] = $element; + } + } + + return array($this->createFrom($matches), $this->createFrom($noMatches)); + } + + /** + * Returns a string representation of this object. + * + * @return string + */ + public function __toString() + { + return __CLASS__ . '@' . spl_object_hash($this); + } + + /** + * {@inheritDoc} + */ + public function clear() + { + $this->elements = array(); + } + + /** + * {@inheritDoc} + */ + public function slice($offset, $length = null) + { + return array_slice($this->elements, $offset, $length, true); + } + + /** + * {@inheritDoc} + */ + public function matching(Criteria $criteria) + { + $expr = $criteria->getWhereExpression(); + $filtered = $this->elements; + + if ($expr) { + $visitor = new ClosureExpressionVisitor(); + $filter = $visitor->dispatch($expr); + $filtered = array_filter($filtered, $filter); + } + + if ($orderings = $criteria->getOrderings()) { + $next = null; + foreach (array_reverse($orderings) as $field => $ordering) { + $next = ClosureExpressionVisitor::sortByField($field, $ordering == Criteria::DESC ? -1 : 1, $next); + } + + uasort($filtered, $next); + } + + $offset = $criteria->getFirstResult(); + $length = $criteria->getMaxResults(); + + if ($offset || $length) { + $filtered = array_slice($filtered, (int)$offset, $length); + } + + return $this->createFrom($filtered); + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php new file mode 100644 index 00000000..8792f7a5 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php @@ -0,0 +1,263 @@ +. + */ + +namespace Doctrine\Common\Collections; + +use ArrayAccess; +use Closure; +use Countable; +use IteratorAggregate; + +/** + * The missing (SPL) Collection/Array/OrderedMap interface. + * + * A Collection resembles the nature of a regular PHP array. That is, + * it is essentially an ordered map that can also be used + * like a list. + * + * A Collection has an internal iterator just like a PHP array. In addition, + * a Collection can be iterated with external iterators, which is preferable. + * To use an external iterator simply use the foreach language construct to + * iterate over the collection (which calls {@link getIterator()} internally) or + * explicitly retrieve an iterator though {@link getIterator()} which can then be + * used to iterate over the collection. + * You can not rely on the internal iterator of the collection being at a certain + * position unless you explicitly positioned it before. Prefer iteration with + * external iterators. + * + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +interface Collection extends Countable, IteratorAggregate, ArrayAccess +{ + /** + * Adds an element at the end of the collection. + * + * @param mixed $element The element to add. + * + * @return boolean Always TRUE. + */ + public function add($element); + + /** + * Clears the collection, removing all elements. + * + * @return void + */ + public function clear(); + + /** + * Checks whether an element is contained in the collection. + * This is an O(n) operation, where n is the size of the collection. + * + * @param mixed $element The element to search for. + * + * @return boolean TRUE if the collection contains the element, FALSE otherwise. + */ + public function contains($element); + + /** + * Checks whether the collection is empty (contains no elements). + * + * @return boolean TRUE if the collection is empty, FALSE otherwise. + */ + public function isEmpty(); + + /** + * Removes the element at the specified index from the collection. + * + * @param string|integer $key The kex/index of the element to remove. + * + * @return mixed The removed element or NULL, if the collection did not contain the element. + */ + public function remove($key); + + /** + * Removes the specified element from the collection, if it is found. + * + * @param mixed $element The element to remove. + * + * @return boolean TRUE if this collection contained the specified element, FALSE otherwise. + */ + public function removeElement($element); + + /** + * Checks whether the collection contains an element with the specified key/index. + * + * @param string|integer $key The key/index to check for. + * + * @return boolean TRUE if the collection contains an element with the specified key/index, + * FALSE otherwise. + */ + public function containsKey($key); + + /** + * Gets the element at the specified key/index. + * + * @param string|integer $key The key/index of the element to retrieve. + * + * @return mixed + */ + public function get($key); + + /** + * Gets all keys/indices of the collection. + * + * @return array The keys/indices of the collection, in the order of the corresponding + * elements in the collection. + */ + public function getKeys(); + + /** + * Gets all values of the collection. + * + * @return array The values of all elements in the collection, in the order they + * appear in the collection. + */ + public function getValues(); + + /** + * Sets an element in the collection at the specified key/index. + * + * @param string|integer $key The key/index of the element to set. + * @param mixed $value The element to set. + * + * @return void + */ + public function set($key, $value); + + /** + * Gets a native PHP array representation of the collection. + * + * @return array + */ + public function toArray(); + + /** + * Sets the internal iterator to the first element in the collection and returns this element. + * + * @return mixed + */ + public function first(); + + /** + * Sets the internal iterator to the last element in the collection and returns this element. + * + * @return mixed + */ + public function last(); + + /** + * Gets the key/index of the element at the current iterator position. + * + * @return int|string + */ + public function key(); + + /** + * Gets the element of the collection at the current iterator position. + * + * @return mixed + */ + public function current(); + + /** + * Moves the internal iterator position to the next element and returns this element. + * + * @return mixed + */ + public function next(); + + /** + * Tests for the existence of an element that satisfies the given predicate. + * + * @param Closure $p The predicate. + * + * @return boolean TRUE if the predicate is TRUE for at least one element, FALSE otherwise. + */ + public function exists(Closure $p); + + /** + * Returns all the elements of this collection that satisfy the predicate p. + * The order of the elements is preserved. + * + * @param Closure $p The predicate used for filtering. + * + * @return Collection A collection with the results of the filter operation. + */ + public function filter(Closure $p); + + /** + * Tests whether the given predicate p holds for all elements of this collection. + * + * @param Closure $p The predicate. + * + * @return boolean TRUE, if the predicate yields TRUE for all elements, FALSE otherwise. + */ + public function forAll(Closure $p); + + /** + * Applies the given function to each element in the collection and returns + * a new collection with the elements returned by the function. + * + * @param Closure $func + * + * @return Collection + */ + public function map(Closure $func); + + /** + * Partitions this collection in two collections according to a predicate. + * Keys are preserved in the resulting collections. + * + * @param Closure $p The predicate on which to partition. + * + * @return array An array with two elements. The first element contains the collection + * of elements where the predicate returned TRUE, the second element + * contains the collection of elements where the predicate returned FALSE. + */ + public function partition(Closure $p); + + /** + * Gets the index/key of a given element. The comparison of two elements is strict, + * that means not only the value but also the type must match. + * For objects this means reference equality. + * + * @param mixed $element The element to search for. + * + * @return int|string|bool The key/index of the element or FALSE if the element was not found. + */ + public function indexOf($element); + + /** + * Extracts a slice of $length elements starting at position $offset from the Collection. + * + * If $length is null it returns all elements from $offset to the end of the Collection. + * Keys have to be preserved by this method. Calling this method will only return the + * selected slice and NOT change the elements contained in the collection slice is called on. + * + * @param int $offset The offset to start from. + * @param int|null $length The maximum number of elements to return, or null for no limit. + * + * @return array + */ + public function slice($offset, $length = null); +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php new file mode 100644 index 00000000..3f9d43a8 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php @@ -0,0 +1,259 @@ +. + */ + +namespace Doctrine\Common\Collections; + +use Doctrine\Common\Collections\Expr\Expression; +use Doctrine\Common\Collections\Expr\CompositeExpression; + +/** + * Criteria for filtering Selectable collections. + * + * @author Benjamin Eberlei + * @since 2.3 + */ +class Criteria +{ + /** + * @var string + */ + const ASC = 'ASC'; + + /** + * @var string + */ + const DESC = 'DESC'; + + /** + * @var \Doctrine\Common\Collections\ExpressionBuilder|null + */ + private static $expressionBuilder; + + /** + * @var \Doctrine\Common\Collections\Expr\Expression|null + */ + private $expression; + + /** + * @var string[] + */ + private $orderings = array(); + + /** + * @var int|null + */ + private $firstResult; + + /** + * @var int|null + */ + private $maxResults; + + /** + * Creates an instance of the class. + * + * @return Criteria + */ + public static function create() + { + return new static(); + } + + /** + * Returns the expression builder. + * + * @return \Doctrine\Common\Collections\ExpressionBuilder + */ + public static function expr() + { + if (self::$expressionBuilder === null) { + self::$expressionBuilder = new ExpressionBuilder(); + } + + return self::$expressionBuilder; + } + + /** + * Construct a new Criteria. + * + * @param Expression $expression + * @param string[]|null $orderings + * @param int|null $firstResult + * @param int|null $maxResults + */ + public function __construct(Expression $expression = null, array $orderings = null, $firstResult = null, $maxResults = null) + { + $this->expression = $expression; + + $this->setFirstResult($firstResult); + $this->setMaxResults($maxResults); + + if (null !== $orderings) { + $this->orderBy($orderings); + } + } + + /** + * Sets the where expression to evaluate when this Criteria is searched for. + * + * @param Expression $expression + * + * @return Criteria + */ + public function where(Expression $expression) + { + $this->expression = $expression; + + return $this; + } + + /** + * Appends the where expression to evaluate when this Criteria is searched for + * using an AND with previous expression. + * + * @param Expression $expression + * + * @return Criteria + */ + public function andWhere(Expression $expression) + { + if ($this->expression === null) { + return $this->where($expression); + } + + $this->expression = new CompositeExpression(CompositeExpression::TYPE_AND, array( + $this->expression, $expression + )); + + return $this; + } + + /** + * Appends the where expression to evaluate when this Criteria is searched for + * using an OR with previous expression. + * + * @param Expression $expression + * + * @return Criteria + */ + public function orWhere(Expression $expression) + { + if ($this->expression === null) { + return $this->where($expression); + } + + $this->expression = new CompositeExpression(CompositeExpression::TYPE_OR, array( + $this->expression, $expression + )); + + return $this; + } + + /** + * Gets the expression attached to this Criteria. + * + * @return Expression|null + */ + public function getWhereExpression() + { + return $this->expression; + } + + /** + * Gets the current orderings of this Criteria. + * + * @return string[] + */ + public function getOrderings() + { + return $this->orderings; + } + + /** + * Sets the ordering of the result of this Criteria. + * + * Keys are field and values are the order, being either ASC or DESC. + * + * @see Criteria::ASC + * @see Criteria::DESC + * + * @param string[] $orderings + * + * @return Criteria + */ + public function orderBy(array $orderings) + { + $this->orderings = array_map( + function ($ordering) { + return strtoupper($ordering) === Criteria::ASC ? Criteria::ASC : Criteria::DESC; + }, + $orderings + ); + + return $this; + } + + /** + * Gets the current first result option of this Criteria. + * + * @return int|null + */ + public function getFirstResult() + { + return $this->firstResult; + } + + /** + * Set the number of first result that this Criteria should return. + * + * @param int|null $firstResult The value to set. + * + * @return Criteria + */ + public function setFirstResult($firstResult) + { + $this->firstResult = null === $firstResult ? null : (int) $firstResult; + + return $this; + } + + /** + * Gets maxResults. + * + * @return int|null + */ + public function getMaxResults() + { + return $this->maxResults; + } + + /** + * Sets maxResults. + * + * @param int|null $maxResults The value to set. + * + * @return Criteria + */ + public function setMaxResults($maxResults) + { + $this->maxResults = null === $maxResults ? null : (int) $maxResults; + + return $this; + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php new file mode 100644 index 00000000..1239aa39 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php @@ -0,0 +1,265 @@ +. + */ + +namespace Doctrine\Common\Collections\Expr; + +/** + * Walks an expression graph and turns it into a PHP closure. + * + * This closure can be used with {@Collection#filter()} and is used internally + * by {@ArrayCollection#select()}. + * + * @author Benjamin Eberlei + * @since 2.3 + */ +class ClosureExpressionVisitor extends ExpressionVisitor +{ + /** + * Accesses the field of a given object. This field has to be public + * directly or indirectly (through an accessor get*, is*, or a magic + * method, __get, __call). + * + * @param object $object + * @param string $field + * + * @return mixed + */ + public static function getObjectFieldValue($object, $field) + { + if (is_array($object)) { + return $object[$field]; + } + + $accessors = array('get', 'is'); + + foreach ($accessors as $accessor) { + $accessor .= $field; + + if ( ! method_exists($object, $accessor)) { + continue; + } + + return $object->$accessor(); + } + + // __call should be triggered for get. + $accessor = $accessors[0] . $field; + + if (method_exists($object, '__call')) { + return $object->$accessor(); + } + + if ($object instanceof \ArrayAccess) { + return $object[$field]; + } + + if (isset($object->$field)) { + return $object->$field; + } + + // camelcase field name to support different variable naming conventions + $ccField = preg_replace_callback('/_(.?)/', function($matches) { return strtoupper($matches[1]); }, $field); + + foreach ($accessors as $accessor) { + $accessor .= $ccField; + + + if ( ! method_exists($object, $accessor)) { + continue; + } + + return $object->$accessor(); + } + + return $object->$field; + } + + /** + * Helper for sorting arrays of objects based on multiple fields + orientations. + * + * @param string $name + * @param int $orientation + * @param \Closure $next + * + * @return \Closure + */ + public static function sortByField($name, $orientation = 1, \Closure $next = null) + { + if ( ! $next) { + $next = function() { + return 0; + }; + } + + return function ($a, $b) use ($name, $next, $orientation) { + $aValue = ClosureExpressionVisitor::getObjectFieldValue($a, $name); + $bValue = ClosureExpressionVisitor::getObjectFieldValue($b, $name); + + if ($aValue === $bValue) { + return $next($a, $b); + } + + return (($aValue > $bValue) ? 1 : -1) * $orientation; + }; + } + + /** + * {@inheritDoc} + */ + public function walkComparison(Comparison $comparison) + { + $field = $comparison->getField(); + $value = $comparison->getValue()->getValue(); // shortcut for walkValue() + + switch ($comparison->getOperator()) { + case Comparison::EQ: + return function ($object) use ($field, $value) { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) === $value; + }; + + case Comparison::NEQ: + return function ($object) use ($field, $value) { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) !== $value; + }; + + case Comparison::LT: + return function ($object) use ($field, $value) { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) < $value; + }; + + case Comparison::LTE: + return function ($object) use ($field, $value) { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) <= $value; + }; + + case Comparison::GT: + return function ($object) use ($field, $value) { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) > $value; + }; + + case Comparison::GTE: + return function ($object) use ($field, $value) { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) >= $value; + }; + + case Comparison::IN: + return function ($object) use ($field, $value) { + return in_array(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value); + }; + + case Comparison::NIN: + return function ($object) use ($field, $value) { + return ! in_array(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value); + }; + + case Comparison::CONTAINS: + return function ($object) use ($field, $value) { + return false !== strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value); + }; + + case Comparison::MEMBER_OF: + return function ($object) use ($field, $value) { + $fieldValues = ClosureExpressionVisitor::getObjectFieldValue($object, $field); + if (!is_array($fieldValues)) { + $fieldValues = iterator_to_array($fieldValues); + } + return in_array($value, $fieldValues); + }; + + case Comparison::STARTS_WITH: + return function ($object) use ($field, $value) { + return 0 === strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value); + }; + + case Comparison::ENDS_WITH: + return function ($object) use ($field, $value) { + return $value === substr(ClosureExpressionVisitor::getObjectFieldValue($object, $field), -strlen($value)); + }; + + + default: + throw new \RuntimeException("Unknown comparison operator: " . $comparison->getOperator()); + } + } + + /** + * {@inheritDoc} + */ + public function walkValue(Value $value) + { + return $value->getValue(); + } + + /** + * {@inheritDoc} + */ + public function walkCompositeExpression(CompositeExpression $expr) + { + $expressionList = array(); + + foreach ($expr->getExpressionList() as $child) { + $expressionList[] = $this->dispatch($child); + } + + switch($expr->getType()) { + case CompositeExpression::TYPE_AND: + return $this->andExpressions($expressionList); + + case CompositeExpression::TYPE_OR: + return $this->orExpressions($expressionList); + + default: + throw new \RuntimeException("Unknown composite " . $expr->getType()); + } + } + + /** + * @param array $expressions + * + * @return callable + */ + private function andExpressions($expressions) + { + return function ($object) use ($expressions) { + foreach ($expressions as $expression) { + if ( ! $expression($object)) { + return false; + } + } + return true; + }; + } + + /** + * @param array $expressions + * + * @return callable + */ + private function orExpressions($expressions) + { + return function ($object) use ($expressions) { + foreach ($expressions as $expression) { + if ($expression($object)) { + return true; + } + } + return false; + }; + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php new file mode 100644 index 00000000..ec9a9f48 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php @@ -0,0 +1,105 @@ +. + */ + +namespace Doctrine\Common\Collections\Expr; + +/** + * Comparison of a field with a value by the given operator. + * + * @author Benjamin Eberlei + * @since 2.3 + */ +class Comparison implements Expression +{ + const EQ = '='; + const NEQ = '<>'; + const LT = '<'; + const LTE = '<='; + const GT = '>'; + const GTE = '>='; + const IS = '='; // no difference with EQ + const IN = 'IN'; + const NIN = 'NIN'; + const CONTAINS = 'CONTAINS'; + const MEMBER_OF = 'MEMBER_OF'; + const STARTS_WITH = 'STARTS_WITH'; + const ENDS_WITH = 'ENDS_WITH'; + /** + * @var string + */ + private $field; + + /** + * @var string + */ + private $op; + + /** + * @var Value + */ + private $value; + + /** + * @param string $field + * @param string $operator + * @param mixed $value + */ + public function __construct($field, $operator, $value) + { + if ( ! ($value instanceof Value)) { + $value = new Value($value); + } + + $this->field = $field; + $this->op = $operator; + $this->value = $value; + } + + /** + * @return string + */ + public function getField() + { + return $this->field; + } + + /** + * @return Value + */ + public function getValue() + { + return $this->value; + } + + /** + * @return string + */ + public function getOperator() + { + return $this->op; + } + + /** + * {@inheritDoc} + */ + public function visit(ExpressionVisitor $visitor) + { + return $visitor->walkComparison($this); + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php new file mode 100644 index 00000000..3613c027 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php @@ -0,0 +1,90 @@ +. + */ + +namespace Doctrine\Common\Collections\Expr; + +/** + * Expression of Expressions combined by AND or OR operation. + * + * @author Benjamin Eberlei + * @since 2.3 + */ +class CompositeExpression implements Expression +{ + const TYPE_AND = 'AND'; + const TYPE_OR = 'OR'; + + /** + * @var string + */ + private $type; + + /** + * @var Expression[] + */ + private $expressions = array(); + + /** + * @param string $type + * @param array $expressions + * + * @throws \RuntimeException + */ + public function __construct($type, array $expressions) + { + $this->type = $type; + + foreach ($expressions as $expr) { + if ($expr instanceof Value) { + throw new \RuntimeException("Values are not supported expressions as children of and/or expressions."); + } + if ( ! ($expr instanceof Expression)) { + throw new \RuntimeException("No expression given to CompositeExpression."); + } + + $this->expressions[] = $expr; + } + } + + /** + * Returns the list of expressions nested in this composite. + * + * @return Expression[] + */ + public function getExpressionList() + { + return $this->expressions; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * {@inheritDoc} + */ + public function visit(ExpressionVisitor $visitor) + { + return $visitor->walkCompositeExpression($this); + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php new file mode 100644 index 00000000..68db767c --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php @@ -0,0 +1,35 @@ +. + */ + +namespace Doctrine\Common\Collections\Expr; + +/** + * Expression for the {@link Selectable} interface. + * + * @author Benjamin Eberlei + */ +interface Expression +{ + /** + * @param ExpressionVisitor $visitor + * + * @return mixed + */ + public function visit(ExpressionVisitor $visitor); +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ExpressionVisitor.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ExpressionVisitor.php new file mode 100644 index 00000000..080afdc6 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ExpressionVisitor.php @@ -0,0 +1,82 @@ +. + */ + +namespace Doctrine\Common\Collections\Expr; + +/** + * An Expression visitor walks a graph of expressions and turns them into a + * query for the underlying implementation. + * + * @author Benjamin Eberlei + */ +abstract class ExpressionVisitor +{ + /** + * Converts a comparison expression into the target query language output. + * + * @param Comparison $comparison + * + * @return mixed + */ + abstract public function walkComparison(Comparison $comparison); + + /** + * Converts a value expression into the target query language part. + * + * @param Value $value + * + * @return mixed + */ + abstract public function walkValue(Value $value); + + /** + * Converts a composite expression into the target query language output. + * + * @param CompositeExpression $expr + * + * @return mixed + */ + abstract public function walkCompositeExpression(CompositeExpression $expr); + + /** + * Dispatches walking an expression to the appropriate handler. + * + * @param Expression $expr + * + * @return mixed + * + * @throws \RuntimeException + */ + public function dispatch(Expression $expr) + { + switch (true) { + case ($expr instanceof Comparison): + return $this->walkComparison($expr); + + case ($expr instanceof Value): + return $this->walkValue($expr); + + case ($expr instanceof CompositeExpression): + return $this->walkCompositeExpression($expr); + + default: + throw new \RuntimeException("Unknown Expression " . get_class($expr)); + } + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php new file mode 100644 index 00000000..7f6e8314 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php @@ -0,0 +1,52 @@ +. + */ + +namespace Doctrine\Common\Collections\Expr; + +class Value implements Expression +{ + /** + * @var mixed + */ + private $value; + + /** + * @param mixed $value + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * {@inheritDoc} + */ + public function visit(ExpressionVisitor $visitor) + { + return $visitor->walkValue($this); + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php new file mode 100644 index 00000000..1a44a7ba --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php @@ -0,0 +1,200 @@ +. + */ + +namespace Doctrine\Common\Collections; + +use Doctrine\Common\Collections\Expr\Comparison; +use Doctrine\Common\Collections\Expr\CompositeExpression; +use Doctrine\Common\Collections\Expr\Value; + +/** + * Builder for Expressions in the {@link Selectable} interface. + * + * Important Notice for interoperable code: You have to use scalar + * values only for comparisons, otherwise the behavior of the comparison + * may be different between implementations (Array vs ORM vs ODM). + * + * @author Benjamin Eberlei + * @since 2.3 + */ +class ExpressionBuilder +{ + /** + * @param mixed $x + * + * @return CompositeExpression + */ + public function andX($x = null) + { + return new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args()); + } + + /** + * @param mixed $x + * + * @return CompositeExpression + */ + public function orX($x = null) + { + return new CompositeExpression(CompositeExpression::TYPE_OR, func_get_args()); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function eq($field, $value) + { + return new Comparison($field, Comparison::EQ, new Value($value)); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function gt($field, $value) + { + return new Comparison($field, Comparison::GT, new Value($value)); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function lt($field, $value) + { + return new Comparison($field, Comparison::LT, new Value($value)); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function gte($field, $value) + { + return new Comparison($field, Comparison::GTE, new Value($value)); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function lte($field, $value) + { + return new Comparison($field, Comparison::LTE, new Value($value)); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function neq($field, $value) + { + return new Comparison($field, Comparison::NEQ, new Value($value)); + } + + /** + * @param string $field + * + * @return Comparison + */ + public function isNull($field) + { + return new Comparison($field, Comparison::EQ, new Value(null)); + } + + /** + * @param string $field + * @param mixed $values + * + * @return Comparison + */ + public function in($field, array $values) + { + return new Comparison($field, Comparison::IN, new Value($values)); + } + + /** + * @param string $field + * @param mixed $values + * + * @return Comparison + */ + public function notIn($field, array $values) + { + return new Comparison($field, Comparison::NIN, new Value($values)); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function contains($field, $value) + { + return new Comparison($field, Comparison::CONTAINS, new Value($value)); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function memberOf ($field, $value) + { + return new Comparison($field, Comparison::MEMBER_OF, new Value($value)); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function startsWith($field, $value) + { + return new Comparison($field, Comparison::STARTS_WITH, new Value($value)); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function endsWith($field, $value) + { + return new Comparison($field, Comparison::ENDS_WITH, new Value($value)); + } + +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Selectable.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Selectable.php new file mode 100644 index 00000000..57660ed7 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Selectable.php @@ -0,0 +1,48 @@ +. + */ + +namespace Doctrine\Common\Collections; + +/** + * Interface for collections that allow efficient filtering with an expression API. + * + * Goal of this interface is a backend independent method to fetch elements + * from a collections. {@link Expression} is crafted in a way that you can + * implement queries from both in-memory and database-backed collections. + * + * For database backed collections this allows very efficient access by + * utilizing the query APIs, for example SQL in the ORM. Applications using + * this API can implement efficient database access without having to ask the + * EntityManager or Repositories. + * + * @author Benjamin Eberlei + * @since 2.3 + */ +interface Selectable +{ + /** + * Selects all elements from a selectable that match the expression and + * returns a new collection containing these elements. + * + * @param Criteria $criteria + * + * @return Collection + */ + public function matching(Criteria $criteria); +} diff --git a/vendor/doctrine/common/LICENSE b/vendor/doctrine/common/LICENSE new file mode 100644 index 00000000..8c38cc1b --- /dev/null +++ b/vendor/doctrine/common/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2015 Doctrine Project + +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/vendor/doctrine/common/README.md b/vendor/doctrine/common/README.md new file mode 100644 index 00000000..30a080f7 --- /dev/null +++ b/vendor/doctrine/common/README.md @@ -0,0 +1,12 @@ +# Doctrine Common + +[![Build Status](https://secure.travis-ci.org/doctrine/common.png)](http://travis-ci.org/doctrine/common) + +The Doctrine Common project is a library that provides extensions to core PHP functionality. + +## More resources: + +* [Website](http://www.doctrine-project.org) +* [Documentation](http://docs.doctrine-project.org/projects/doctrine-common/en/latest/) +* [Issue Tracker](http://www.doctrine-project.org/jira/browse/DCOM) +* [Downloads](http://github.com/doctrine/common/downloads) diff --git a/vendor/doctrine/common/UPGRADE_TO_2_1 b/vendor/doctrine/common/UPGRADE_TO_2_1 new file mode 100644 index 00000000..891a2e5c --- /dev/null +++ b/vendor/doctrine/common/UPGRADE_TO_2_1 @@ -0,0 +1,39 @@ +This document details all the possible changes that you should investigate when updating +your project from Doctrine Common 2.0.x to 2.1 + +## AnnotationReader changes + +The annotation reader was heavily refactored between 2.0 and 2.1-RC1. In theory the operation of the new reader should be backwards compatible, but it has to be setup differently to work that way: + + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); + $reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\'); + // new code necessary starting here + $reader->setIgnoreNotImportedAnnotations(true); + $reader->setEnableParsePhpImports(false); + $reader = new \Doctrine\Common\Annotations\CachedReader( + new \Doctrine\Common\Annotations\IndexedReader($reader), new ArrayCache() + ); + +## Annotation Base class or @Annotation + +Beginning after 2.1-RC2 you have to either extend ``Doctrine\Common\Annotations\Annotation`` or add @Annotation to your annotations class-level docblock, otherwise the class will simply be ignored. + +## Removed methods on AnnotationReader + +* AnnotationReader::setAutoloadAnnotations() +* AnnotationReader::getAutoloadAnnotations() +* AnnotationReader::isAutoloadAnnotations() + +## AnnotationRegistry + +Autoloading through the PHP autoloader is removed from the 2.1 AnnotationReader. Instead you have to use the global AnnotationRegistry for loading purposes: + + \Doctrine\Common\Annotations\AnnotationRegistry::registerFile($fileWithAnnotations); + \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace($namespace, $dirs = null); + \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespaces($namespaces); + \Doctrine\Common\Annotations\AnnotationRegistry::registerLoader($callable); + +The $callable for registering a loader accepts a class as first and only parameter and must try to silently autoload it. On success true has to be returned. +The registerAutoloadNamespace function registers a PSR-0 compatible silent autoloader for all classes with the given namespace in the given directories. +If null is passed as directory the include path will be used. + diff --git a/vendor/doctrine/common/UPGRADE_TO_2_2 b/vendor/doctrine/common/UPGRADE_TO_2_2 new file mode 100644 index 00000000..1d93a131 --- /dev/null +++ b/vendor/doctrine/common/UPGRADE_TO_2_2 @@ -0,0 +1,61 @@ +This document details all the possible changes that you should investigate when +updating your project from Doctrine Common 2.1 to 2.2: + +## Annotation Changes + +- AnnotationReader::setIgnoreNotImportedAnnotations has been removed, you need to + add ignore annotation names which are supposed to be ignored via + AnnotationReader::addGlobalIgnoredName + +- AnnotationReader::setAutoloadAnnotations was deprecated by the AnnotationRegistry + in 2.1 and has been removed in 2.2 + +- AnnotationReader::setEnableParsePhpImports was added to ease transition to the new + annotation mechanism in 2.1 and is removed in 2.2 + +- AnnotationReader::isParsePhpImportsEnabled is removed (see above) + +- AnnotationReader::setDefaultAnnotationNamespace was deprecated in favor of explicit + configuration in 2.1 and will be removed in 2.2 (for isolated projects where you + have full-control over _all_ available annotations, we offer a dedicated reader + class ``SimpleAnnotationReader``) + +- AnnotationReader::setAnnotationCreationFunction was deprecated in 2.1 and will be + removed in 2.2. We only offer two creation mechanisms which cannot be changed + anymore to allow the same reader instance to work with all annotations regardless + of which library they are coming from. + +- AnnotationReader::setAnnotationNamespaceAlias was deprecated in 2.1 and will be + removed in 2.2 (see setDefaultAnnotationNamespace) + +- If you use a class as annotation which has not the @Annotation marker in it's + class block, we will now throw an exception instead of silently ignoring it. You + can however still achieve the previous behavior using the @IgnoreAnnotation, or + AnnotationReader::addGlobalIgnoredName (the exception message will contain detailed + instructions when you run into this problem). + +## Cache Changes + +- Renamed old AbstractCache to CacheProvider + +- Dropped the support to the following functions of all cache providers: + + - CacheProvider::deleteByWildcard + + - CacheProvider::deleteByRegEx + + - CacheProvider::deleteByPrefix + + - CacheProvider::deleteBySuffix + +- CacheProvider::deleteAll will not remove ALL entries, it will only mark them as invalid + +- CacheProvider::flushAll will remove ALL entries, namespaced or not + +- Added support to MemcachedCache + +- Added support to WincacheCache + +## ClassLoader Changes + +- ClassLoader::fileExistsInIncludePath() no longer exists. Use the native stream_resolve_include_path() PHP function \ No newline at end of file diff --git a/vendor/doctrine/common/composer.json b/vendor/doctrine/common/composer.json new file mode 100644 index 00000000..7729b168 --- /dev/null +++ b/vendor/doctrine/common/composer.json @@ -0,0 +1,36 @@ +{ + "name": "doctrine/common", + "type": "library", + "description": "Common Library for Doctrine projects", + "keywords": ["collections", "spl", "eventmanager", "annotations", "persistence"], + "homepage": "http://www.doctrine-project.org", + "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"} + ], + "require": { + "php": "~5.6|~7.0", + "doctrine/inflector": "1.*", + "doctrine/cache": "1.*", + "doctrine/collections": "1.*", + "doctrine/lexer": "1.*", + "doctrine/annotations": "1.*" + }, + "require-dev": { + "phpunit/phpunit": "^5.4.6" + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.7.x-dev" + } + } +} diff --git a/vendor/doctrine/common/humbug.json.dist b/vendor/doctrine/common/humbug.json.dist new file mode 100644 index 00000000..ed6732a3 --- /dev/null +++ b/vendor/doctrine/common/humbug.json.dist @@ -0,0 +1,11 @@ +{ + "source": { + "directories": [ + "lib\/Doctrine" + ] + }, + "timeout": 10, + "logs": { + "text": "reports/humbuglog.txt" + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/ClassLoader.php b/vendor/doctrine/common/lib/Doctrine/Common/ClassLoader.php new file mode 100644 index 00000000..78eeb35d --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/ClassLoader.php @@ -0,0 +1,280 @@ +. + */ + +namespace Doctrine\Common; + +/** + * A ClassLoader is an autoloader for class files that can be + * installed on the SPL autoload stack. It is a class loader that either loads only classes + * of a specific namespace or all namespaces and it is suitable for working together + * with other autoloaders in the SPL autoload stack. + * + * If no include path is configured through the constructor or {@link setIncludePath}, a ClassLoader + * relies on the PHP include_path. + * + * @author Roman Borschel + * @since 2.0 + * + * @deprecated the ClassLoader is deprecated and will be removed in version 3.0 of doctrine/common. + */ +class ClassLoader +{ + /** + * PHP file extension. + * + * @var string + */ + protected $fileExtension = '.php'; + + /** + * Current namespace. + * + * @var string|null + */ + protected $namespace; + + /** + * Current include path. + * + * @var string|null + */ + protected $includePath; + + /** + * PHP namespace separator. + * + * @var string + */ + protected $namespaceSeparator = '\\'; + + /** + * Creates a new ClassLoader that loads classes of the + * specified namespace from the specified include path. + * + * If no include path is given, the ClassLoader relies on the PHP include_path. + * If neither a namespace nor an include path is given, the ClassLoader will + * be responsible for loading all classes, thereby relying on the PHP include_path. + * + * @param string|null $ns The namespace of the classes to load. + * @param string|null $includePath The base include path to use. + */ + public function __construct($ns = null, $includePath = null) + { + $this->namespace = $ns; + $this->includePath = $includePath; + } + + /** + * Sets the namespace separator used by classes in the namespace of this ClassLoader. + * + * @param string $sep The separator to use. + * + * @return void + */ + public function setNamespaceSeparator($sep) + { + $this->namespaceSeparator = $sep; + } + + /** + * Gets the namespace separator used by classes in the namespace of this ClassLoader. + * + * @return string + */ + public function getNamespaceSeparator() + { + return $this->namespaceSeparator; + } + + /** + * Sets the base include path for all class files in the namespace of this ClassLoader. + * + * @param string|null $includePath + * + * @return void + */ + public function setIncludePath($includePath) + { + $this->includePath = $includePath; + } + + /** + * Gets the base include path for all class files in the namespace of this ClassLoader. + * + * @return string|null + */ + public function getIncludePath() + { + return $this->includePath; + } + + /** + * Sets the file extension of class files in the namespace of this ClassLoader. + * + * @param string $fileExtension + * + * @return void + */ + public function setFileExtension($fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * Gets the file extension of class files in the namespace of this ClassLoader. + * + * @return string + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Registers this ClassLoader on the SPL autoload stack. + * + * @return void + */ + public function register() + { + spl_autoload_register([$this, 'loadClass']); + } + + /** + * Removes this ClassLoader from the SPL autoload stack. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister([$this, 'loadClass']); + } + + /** + * Loads the given class or interface. + * + * @param string $className The name of the class to load. + * + * @return boolean TRUE if the class has been successfully loaded, FALSE otherwise. + */ + public function loadClass($className) + { + if (self::typeExists($className)) { + return true; + } + + if (! $this->canLoadClass($className)) { + return false; + } + + require ($this->includePath !== null ? $this->includePath . DIRECTORY_SEPARATOR : '') + . str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $className) + . $this->fileExtension; + + return self::typeExists($className); + } + + /** + * Asks this ClassLoader whether it can potentially load the class (file) with + * the given name. + * + * @param string $className The fully-qualified name of the class. + * + * @return boolean TRUE if this ClassLoader can load the class, FALSE otherwise. + */ + public function canLoadClass($className) + { + if ($this->namespace !== null && strpos($className, $this->namespace.$this->namespaceSeparator) !== 0) { + return false; + } + + $file = str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $className) . $this->fileExtension; + + if ($this->includePath !== null) { + return is_file($this->includePath . DIRECTORY_SEPARATOR . $file); + } + + return (false !== stream_resolve_include_path($file)); + } + + /** + * Checks whether a class with a given name exists. A class "exists" if it is either + * already defined in the current request or if there is an autoloader on the SPL + * autoload stack that is a) responsible for the class in question and b) is able to + * load a class file in which the class definition resides. + * + * If the class is not already defined, each autoloader in the SPL autoload stack + * is asked whether it is able to tell if the class exists. If the autoloader is + * a ClassLoader, {@link canLoadClass} is used, otherwise the autoload + * function of the autoloader is invoked and expected to return a value that + * evaluates to TRUE if the class (file) exists. As soon as one autoloader reports + * that the class exists, TRUE is returned. + * + * Note that, depending on what kinds of autoloaders are installed on the SPL + * autoload stack, the class (file) might already be loaded as a result of checking + * for its existence. This is not the case with a ClassLoader, who separates + * these responsibilities. + * + * @param string $className The fully-qualified name of the class. + * + * @return boolean TRUE if the class exists as per the definition given above, FALSE otherwise. + */ + public static function classExists($className) + { + return self::typeExists($className, true); + } + + /** + * Gets the ClassLoader from the SPL autoload stack that is responsible + * for (and is able to load) the class with the given name. + * + * @param string $className The name of the class. + * + * @return ClassLoader The ClassLoader for the class or NULL if no such ClassLoader exists. + */ + public static function getClassLoader($className) + { + foreach (spl_autoload_functions() as $loader) { + if (is_array($loader) + && ($classLoader = reset($loader)) + && $classLoader instanceof ClassLoader + && $classLoader->canLoadClass($className) + ) { + return $classLoader; + } + } + + return null; + } + + /** + * Checks whether a given type exists + * + * @param string $type + * @param bool $autoload + * + * @return bool + */ + private static function typeExists($type, $autoload = false) + { + return class_exists($type, $autoload) + || interface_exists($type, $autoload) + || trait_exists($type, $autoload); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/CommonException.php b/vendor/doctrine/common/lib/Doctrine/Common/CommonException.php new file mode 100644 index 00000000..2a1a08e7 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/CommonException.php @@ -0,0 +1,29 @@ +. + */ + +namespace Doctrine\Common; + +/** + * Base exception class for package Doctrine\Common. + * + * @author heinrich + */ +class CommonException extends \Exception +{ +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Comparable.php b/vendor/doctrine/common/lib/Doctrine/Common/Comparable.php new file mode 100644 index 00000000..8cd02c9f --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Comparable.php @@ -0,0 +1,46 @@ +. + */ + +namespace Doctrine\Common; + +/** + * Comparable interface that allows to compare two value objects to each other for similarity. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + */ +interface Comparable +{ + /** + * Compares the current object to the passed $other. + * + * Returns 0 if they are semantically equal, 1 if the other object + * is less than the current one, or -1 if its more than the current one. + * + * This method should not check for identity using ===, only for semantical equality for example + * when two different DateTime instances point to the exact same Date + TZ. + * + * @param mixed $other + * + * @return int + */ + public function compareTo($other); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/EventArgs.php b/vendor/doctrine/common/lib/Doctrine/Common/EventArgs.php new file mode 100644 index 00000000..75506e6a --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/EventArgs.php @@ -0,0 +1,67 @@ +. + */ + +namespace Doctrine\Common; + +/** + * EventArgs is the base class for classes containing event data. + * + * This class contains no event data. It is used by events that do not pass state + * information to an event handler when an event is raised. The single empty EventArgs + * instance can be obtained through {@link getEmptyInstance}. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class EventArgs +{ + /** + * Single instance of EventArgs. + * + * @var EventArgs + */ + private static $_emptyEventArgsInstance; + + /** + * Gets the single, empty and immutable EventArgs instance. + * + * This instance will be used when events are dispatched without any parameter, + * like this: EventManager::dispatchEvent('eventname'); + * + * The benefit from this is that only one empty instance is instantiated and shared + * (otherwise there would be instances for every dispatched in the abovementioned form). + * + * @see EventManager::dispatchEvent + * + * @link http://msdn.microsoft.com/en-us/library/system.eventargs.aspx + * + * @return EventArgs + */ + public static function getEmptyInstance() + { + if ( ! self::$_emptyEventArgsInstance) { + self::$_emptyEventArgsInstance = new EventArgs; + } + + return self::$_emptyEventArgsInstance; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/EventManager.php b/vendor/doctrine/common/lib/Doctrine/Common/EventManager.php new file mode 100644 index 00000000..0ee04a15 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/EventManager.php @@ -0,0 +1,154 @@ +. + */ + +namespace Doctrine\Common; + +/** + * The EventManager is the central point of Doctrine's event listener system. + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class EventManager +{ + /** + * Map of registered listeners. + * => + * + * @var array + */ + private $_listeners = []; + + /** + * Dispatches an event to all registered listeners. + * + * @param string $eventName The name of the event to dispatch. The name of the event is + * the name of the method that is invoked on listeners. + * @param EventArgs|null $eventArgs The event arguments to pass to the event handlers/listeners. + * If not supplied, the single empty EventArgs instance is used. + * + * @return boolean + */ + public function dispatchEvent($eventName, EventArgs $eventArgs = null) + { + if (isset($this->_listeners[$eventName])) { + $eventArgs = $eventArgs === null ? EventArgs::getEmptyInstance() : $eventArgs; + + foreach ($this->_listeners[$eventName] as $listener) { + $listener->$eventName($eventArgs); + } + } + } + + /** + * Gets the listeners of a specific event or all listeners. + * + * @param string|null $event The name of the event. + * + * @return array The event listeners for the specified event, or all event listeners. + */ + public function getListeners($event = null) + { + return $event ? $this->_listeners[$event] : $this->_listeners; + } + + /** + * Checks whether an event has any registered listeners. + * + * @param string $event + * + * @return boolean TRUE if the specified event has any listeners, FALSE otherwise. + */ + public function hasListeners($event) + { + return isset($this->_listeners[$event]) && $this->_listeners[$event]; + } + + /** + * Adds an event listener that listens on the specified events. + * + * @param string|array $events The event(s) to listen on. + * @param object $listener The listener object. + * + * @return void + */ + public function addEventListener($events, $listener) + { + // Picks the hash code related to that listener + $hash = spl_object_hash($listener); + + foreach ((array) $events as $event) { + // Overrides listener if a previous one was associated already + // Prevents duplicate listeners on same event (same instance only) + $this->_listeners[$event][$hash] = $listener; + } + } + + /** + * Removes an event listener from the specified events. + * + * @param string|array $events + * @param object $listener + * + * @return void + */ + public function removeEventListener($events, $listener) + { + // Picks the hash code related to that listener + $hash = spl_object_hash($listener); + + foreach ((array) $events as $event) { + // Check if actually have this listener associated + if (isset($this->_listeners[$event][$hash])) { + unset($this->_listeners[$event][$hash]); + } + } + } + + /** + * Adds an EventSubscriber. The subscriber is asked for all the events it is + * interested in and added as a listener for these events. + * + * @param \Doctrine\Common\EventSubscriber $subscriber The subscriber. + * + * @return void + */ + public function addEventSubscriber(EventSubscriber $subscriber) + { + $this->addEventListener($subscriber->getSubscribedEvents(), $subscriber); + } + + /** + * Removes an EventSubscriber. The subscriber is asked for all the events it is + * interested in and removed as a listener for these events. + * + * @param \Doctrine\Common\EventSubscriber $subscriber The subscriber. + * + * @return void + */ + public function removeEventSubscriber(EventSubscriber $subscriber) + { + $this->removeEventListener($subscriber->getSubscribedEvents(), $subscriber); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/EventSubscriber.php b/vendor/doctrine/common/lib/Doctrine/Common/EventSubscriber.php new file mode 100644 index 00000000..55d0f7d0 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/EventSubscriber.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\Common; + +/** + * An EventSubscriber knows himself what events he is interested in. + * If an EventSubscriber is added to an EventManager, the manager invokes + * {@link getSubscribedEvents} and registers the subscriber as a listener for all + * returned events. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +interface EventSubscriber +{ + /** + * Returns an array of events this subscriber wants to listen to. + * + * @return array + */ + public function getSubscribedEvents(); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Lexer.php b/vendor/doctrine/common/lib/Doctrine/Common/Lexer.php new file mode 100644 index 00000000..0aa07f85 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Lexer.php @@ -0,0 +1,37 @@ +. + */ + +namespace Doctrine\Common; + +use Doctrine\Common\Lexer\AbstractLexer; + +/** + * Base class for writing simple lexers, i.e. for creating small DSLs. + * + * Lexer moved into its own Component Doctrine\Common\Lexer. This class + * only stays for being BC. + * + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +abstract class Lexer extends AbstractLexer +{ +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/NotifyPropertyChanged.php b/vendor/doctrine/common/lib/Doctrine/Common/NotifyPropertyChanged.php new file mode 100644 index 00000000..e25e9997 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/NotifyPropertyChanged.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\Common; + +/** + * Contract for classes that provide the service of notifying listeners of + * changes to their properties. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +interface NotifyPropertyChanged +{ + /** + * Adds a listener that wants to be notified about property changes. + * + * @param PropertyChangedListener $listener + * + * @return void + */ + public function addPropertyChangedListener(PropertyChangedListener $listener); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/AbstractManagerRegistry.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/AbstractManagerRegistry.php new file mode 100644 index 00000000..6da54223 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/AbstractManagerRegistry.php @@ -0,0 +1,264 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +/** + * Abstract implementation of the ManagerRegistry contract. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Fabien Potencier + * @author Benjamin Eberlei + * @author Lukas Kahwe Smith + */ +abstract class AbstractManagerRegistry implements ManagerRegistry +{ + /** + * @var string + */ + private $name; + + /** + * @var array + */ + private $connections; + + /** + * @var array + */ + private $managers; + + /** + * @var string + */ + private $defaultConnection; + + /** + * @var string + */ + private $defaultManager; + + /** + * @var string + */ + private $proxyInterfaceName; + + /** + * Constructor. + * + * @param string $name + * @param array $connections + * @param array $managers + * @param string $defaultConnection + * @param string $defaultManager + * @param string $proxyInterfaceName + */ + public function __construct($name, array $connections, array $managers, $defaultConnection, $defaultManager, $proxyInterfaceName) + { + $this->name = $name; + $this->connections = $connections; + $this->managers = $managers; + $this->defaultConnection = $defaultConnection; + $this->defaultManager = $defaultManager; + $this->proxyInterfaceName = $proxyInterfaceName; + } + + /** + * Fetches/creates the given services. + * + * A service in this context is connection or a manager instance. + * + * @param string $name The name of the service. + * + * @return object The instance of the given service. + */ + abstract protected function getService($name); + + /** + * Resets the given services. + * + * A service in this context is connection or a manager instance. + * + * @param string $name The name of the service. + * + * @return void + */ + abstract protected function resetService($name); + + /** + * Gets the name of the registry. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getConnection($name = null) + { + if (null === $name) { + $name = $this->defaultConnection; + } + + if (!isset($this->connections[$name])) { + throw new \InvalidArgumentException(sprintf('Doctrine %s Connection named "%s" does not exist.', $this->name, $name)); + } + + return $this->getService($this->connections[$name]); + } + + /** + * {@inheritdoc} + */ + public function getConnectionNames() + { + return $this->connections; + } + + /** + * {@inheritdoc} + */ + public function getConnections() + { + $connections = []; + foreach ($this->connections as $name => $id) { + $connections[$name] = $this->getService($id); + } + + return $connections; + } + + /** + * {@inheritdoc} + */ + public function getDefaultConnectionName() + { + return $this->defaultConnection; + } + + /** + * {@inheritdoc} + */ + public function getDefaultManagerName() + { + return $this->defaultManager; + } + + /** + * {@inheritdoc} + * + * @throws \InvalidArgumentException + */ + public function getManager($name = null) + { + if (null === $name) { + $name = $this->defaultManager; + } + + if (!isset($this->managers[$name])) { + throw new \InvalidArgumentException(sprintf('Doctrine %s Manager named "%s" does not exist.', $this->name, $name)); + } + + return $this->getService($this->managers[$name]); + } + + /** + * {@inheritdoc} + */ + public function getManagerForClass($class) + { + // Check for namespace alias + if (strpos($class, ':') !== false) { + list($namespaceAlias, $simpleClassName) = explode(':', $class, 2); + $class = $this->getAliasNamespace($namespaceAlias) . '\\' . $simpleClassName; + } + + $proxyClass = new \ReflectionClass($class); + + if ($proxyClass->implementsInterface($this->proxyInterfaceName)) { + if (! $parentClass = $proxyClass->getParentClass()) { + return null; + } + + $class = $parentClass->getName(); + } + + foreach ($this->managers as $id) { + $manager = $this->getService($id); + + if (!$manager->getMetadataFactory()->isTransient($class)) { + return $manager; + } + } + } + + /** + * {@inheritdoc} + */ + public function getManagerNames() + { + return $this->managers; + } + + /** + * {@inheritdoc} + */ + public function getManagers() + { + $dms = []; + foreach ($this->managers as $name => $id) { + $dms[$name] = $this->getService($id); + } + + return $dms; + } + + /** + * {@inheritdoc} + */ + public function getRepository($persistentObjectName, $persistentManagerName = null) + { + return $this->getManager($persistentManagerName)->getRepository($persistentObjectName); + } + + /** + * {@inheritdoc} + */ + public function resetManager($name = null) + { + if (null === $name) { + $name = $this->defaultManager; + } + + if (!isset($this->managers[$name])) { + throw new \InvalidArgumentException(sprintf('Doctrine %s Manager named "%s" does not exist.', $this->name, $name)); + } + + // force the creation of a new document manager + // if the current one is closed + $this->resetService($this->managers[$name]); + + return $this->getManager($name); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ConnectionRegistry.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ConnectionRegistry.php new file mode 100644 index 00000000..7c25e98a --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ConnectionRegistry.php @@ -0,0 +1,62 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +/** + * Contract covering connection for a Doctrine persistence layer ManagerRegistry class to implement. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Fabien Potencier + * @author Benjamin Eberlei + * @author Lukas Kahwe Smith + */ +interface ConnectionRegistry +{ + /** + * Gets the default connection name. + * + * @return string The default connection name. + */ + public function getDefaultConnectionName(); + + /** + * Gets the named connection. + * + * @param string $name The connection name (null for the default one). + * + * @return object + */ + public function getConnection($name = null); + + /** + * Gets an array of all registered connections. + * + * @return array An array of Connection instances. + */ + public function getConnections(); + + /** + * Gets all connection names. + * + * @return array An array of connection names. + */ + public function getConnectionNames(); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/LifecycleEventArgs.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/LifecycleEventArgs.php new file mode 100644 index 00000000..52f41c0e --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/LifecycleEventArgs.php @@ -0,0 +1,89 @@ +. + */ + +namespace Doctrine\Common\Persistence\Event; + +use Doctrine\Common\EventArgs; +use Doctrine\Common\Persistence\ObjectManager; + +/** + * Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions + * of entities. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Roman Borschel + * @author Benjamin Eberlei + */ +class LifecycleEventArgs extends EventArgs +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var object + */ + private $object; + + /** + * Constructor. + * + * @param object $object + * @param ObjectManager $objectManager + */ + public function __construct($object, ObjectManager $objectManager) + { + $this->object = $object; + $this->objectManager = $objectManager; + } + + /** + * Retrieves the associated entity. + * + * @deprecated + * + * @return object + */ + public function getEntity() + { + return $this->object; + } + + /** + * Retrieves the associated object. + * + * @return object + */ + public function getObject() + { + return $this->object; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return ObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/LoadClassMetadataEventArgs.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/LoadClassMetadataEventArgs.php new file mode 100644 index 00000000..3d8abbe2 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/LoadClassMetadataEventArgs.php @@ -0,0 +1,75 @@ +. + */ + +namespace Doctrine\Common\Persistence\Event; + +use Doctrine\Common\EventArgs; +use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\Common\Persistence\ObjectManager; + +/** + * Class that holds event arguments for a loadMetadata event. + * + * @author Jonathan H. Wage + * @since 2.2 + */ +class LoadClassMetadataEventArgs extends EventArgs +{ + /** + * @var ClassMetadata + */ + private $classMetadata; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * Constructor. + * + * @param ClassMetadata $classMetadata + * @param ObjectManager $objectManager + */ + public function __construct(ClassMetadata $classMetadata, ObjectManager $objectManager) + { + $this->classMetadata = $classMetadata; + $this->objectManager = $objectManager; + } + + /** + * Retrieves the associated ClassMetadata. + * + * @return ClassMetadata + */ + public function getClassMetadata() + { + return $this->classMetadata; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return ObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/ManagerEventArgs.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/ManagerEventArgs.php new file mode 100644 index 00000000..5527d4d4 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/ManagerEventArgs.php @@ -0,0 +1,59 @@ +. + */ + +namespace Doctrine\Common\Persistence\Event; + +use Doctrine\Common\EventArgs; +use Doctrine\Common\Persistence\ObjectManager; + +/** + * Provides event arguments for the preFlush event. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Roman Borschel + * @author Benjamin Eberlei + */ +class ManagerEventArgs extends EventArgs +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * Constructor. + * + * @param ObjectManager $objectManager + */ + public function __construct(ObjectManager $objectManager) + { + $this->objectManager = $objectManager; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return ObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/OnClearEventArgs.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/OnClearEventArgs.php new file mode 100644 index 00000000..b78bad99 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/OnClearEventArgs.php @@ -0,0 +1,86 @@ +. + */ + +namespace Doctrine\Common\Persistence\Event; + +use Doctrine\Common\EventArgs; +use Doctrine\Common\Persistence\ObjectManager; + +/** + * Provides event arguments for the onClear event. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Roman Borschel + * @author Benjamin Eberlei + */ +class OnClearEventArgs extends EventArgs +{ + /** + * @var \Doctrine\Common\Persistence\ObjectManager + */ + private $objectManager; + + /** + * @var string|null + */ + private $entityClass; + + /** + * Constructor. + * + * @param ObjectManager $objectManager The object manager. + * @param string|null $entityClass The optional entity class. + */ + public function __construct($objectManager, $entityClass = null) + { + $this->objectManager = $objectManager; + $this->entityClass = $entityClass; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return \Doctrine\Common\Persistence\ObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } + + /** + * Returns the name of the entity class that is cleared, or null if all are cleared. + * + * @return string|null + */ + public function getEntityClass() + { + return $this->entityClass; + } + + /** + * Returns whether this event clears all entities. + * + * @return bool + */ + public function clearsAllEntities() + { + return ($this->entityClass === null); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/PreUpdateEventArgs.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/PreUpdateEventArgs.php new file mode 100644 index 00000000..facce647 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/PreUpdateEventArgs.php @@ -0,0 +1,137 @@ +. + */ + +namespace Doctrine\Common\Persistence\Event; + +use Doctrine\Common\Persistence\ObjectManager; + +/** + * Class that holds event arguments for a preUpdate event. + * + * @author Guilherme Blanco + * @author Roman Borschel + * @author Benjamin Eberlei + * @since 2.2 + */ +class PreUpdateEventArgs extends LifecycleEventArgs +{ + /** + * @var array + */ + private $entityChangeSet; + + /** + * Constructor. + * + * @param object $entity + * @param ObjectManager $objectManager + * @param array $changeSet + */ + public function __construct($entity, ObjectManager $objectManager, array &$changeSet) + { + parent::__construct($entity, $objectManager); + + $this->entityChangeSet = &$changeSet; + } + + /** + * Retrieves the entity changeset. + * + * @return array + */ + public function getEntityChangeSet() + { + return $this->entityChangeSet; + } + + /** + * Checks if field has a changeset. + * + * @param string $field + * + * @return boolean + */ + public function hasChangedField($field) + { + return isset($this->entityChangeSet[$field]); + } + + /** + * Gets the old value of the changeset of the changed field. + * + * @param string $field + * + * @return mixed + */ + public function getOldValue($field) + { + $this->assertValidField($field); + + return $this->entityChangeSet[$field][0]; + } + + /** + * Gets the new value of the changeset of the changed field. + * + * @param string $field + * + * @return mixed + */ + public function getNewValue($field) + { + $this->assertValidField($field); + + return $this->entityChangeSet[$field][1]; + } + + /** + * Sets the new value of this field. + * + * @param string $field + * @param mixed $value + * + * @return void + */ + public function setNewValue($field, $value) + { + $this->assertValidField($field); + + $this->entityChangeSet[$field][1] = $value; + } + + /** + * Asserts the field exists in changeset. + * + * @param string $field + * + * @return void + * + * @throws \InvalidArgumentException + */ + private function assertValidField($field) + { + if ( ! isset($this->entityChangeSet[$field])) { + throw new \InvalidArgumentException(sprintf( + 'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.', + $field, + get_class($this->getEntity()) + )); + } + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ManagerRegistry.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ManagerRegistry.php new file mode 100644 index 00000000..fce854b6 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ManagerRegistry.php @@ -0,0 +1,111 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +/** + * Contract covering object managers for a Doctrine persistence layer ManagerRegistry class to implement. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Fabien Potencier + * @author Benjamin Eberlei + * @author Lukas Kahwe Smith + */ +interface ManagerRegistry extends ConnectionRegistry +{ + /** + * Gets the default object manager name. + * + * @return string The default object manager name. + */ + public function getDefaultManagerName(); + + /** + * Gets a named object manager. + * + * @param string $name The object manager name (null for the default one). + * + * @return \Doctrine\Common\Persistence\ObjectManager + */ + public function getManager($name = null); + + /** + * Gets an array of all registered object managers. + * + * @return \Doctrine\Common\Persistence\ObjectManager[] An array of ObjectManager instances + */ + public function getManagers(); + + /** + * Resets a named object manager. + * + * This method is useful when an object manager has been closed + * because of a rollbacked transaction AND when you think that + * it makes sense to get a new one to replace the closed one. + * + * Be warned that you will get a brand new object manager as + * the existing one is not useable anymore. This means that any + * other object with a dependency on this object manager will + * hold an obsolete reference. You can inject the registry instead + * to avoid this problem. + * + * @param string|null $name The object manager name (null for the default one). + * + * @return \Doctrine\Common\Persistence\ObjectManager + */ + public function resetManager($name = null); + + /** + * Resolves a registered namespace alias to the full namespace. + * + * This method looks for the alias in all registered object managers. + * + * @param string $alias The alias. + * + * @return string The full namespace. + */ + public function getAliasNamespace($alias); + + /** + * Gets all connection names. + * + * @return array An array of connection names. + */ + public function getManagerNames(); + + /** + * Gets the ObjectRepository for an persistent object. + * + * @param string $persistentObject The name of the persistent object. + * @param string $persistentManagerName The object manager name (null for the default one). + * + * @return \Doctrine\Common\Persistence\ObjectRepository + */ + public function getRepository($persistentObject, $persistentManagerName = null); + + /** + * Gets the object manager associated with a given class. + * + * @param string $class A persistent object class name. + * + * @return \Doctrine\Common\Persistence\ObjectManager|null + */ + public function getManagerForClass($class); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php new file mode 100644 index 00000000..cba424b0 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php @@ -0,0 +1,429 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping; + +use Doctrine\Common\Cache\Cache; +use Doctrine\Common\Util\ClassUtils; +use ReflectionException; + +/** + * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the + * metadata mapping informations of a class which describes how a class should be mapped + * to a relational database. + * + * This class was abstracted from the ORM ClassMetadataFactory. + * + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +abstract class AbstractClassMetadataFactory implements ClassMetadataFactory +{ + /** + * Salt used by specific Object Manager implementation. + * + * @var string + */ + protected $cacheSalt = '$CLASSMETADATA'; + + /** + * @var \Doctrine\Common\Cache\Cache|null + */ + private $cacheDriver; + + /** + * @var ClassMetadata[] + */ + private $loadedMetadata = []; + + /** + * @var bool + */ + protected $initialized = false; + + /** + * @var ReflectionService|null + */ + private $reflectionService = null; + + /** + * Sets the cache driver used by the factory to cache ClassMetadata instances. + * + * @param \Doctrine\Common\Cache\Cache $cacheDriver + * + * @return void + */ + public function setCacheDriver(Cache $cacheDriver = null) + { + $this->cacheDriver = $cacheDriver; + } + + /** + * Gets the cache driver used by the factory to cache ClassMetadata instances. + * + * @return \Doctrine\Common\Cache\Cache|null + */ + public function getCacheDriver() + { + return $this->cacheDriver; + } + + /** + * Returns an array of all the loaded metadata currently in memory. + * + * @return ClassMetadata[] + */ + public function getLoadedMetadata() + { + return $this->loadedMetadata; + } + + /** + * Forces the factory to load the metadata of all classes known to the underlying + * mapping driver. + * + * @return array The ClassMetadata instances of all mapped classes. + */ + public function getAllMetadata() + { + if ( ! $this->initialized) { + $this->initialize(); + } + + $driver = $this->getDriver(); + $metadata = []; + foreach ($driver->getAllClassNames() as $className) { + $metadata[] = $this->getMetadataFor($className); + } + + return $metadata; + } + + /** + * Lazy initialization of this stuff, especially the metadata driver, + * since these are not needed at all when a metadata cache is active. + * + * @return void + */ + abstract protected function initialize(); + + /** + * Gets the fully qualified class-name from the namespace alias. + * + * @param string $namespaceAlias + * @param string $simpleClassName + * + * @return string + */ + abstract protected function getFqcnFromAlias($namespaceAlias, $simpleClassName); + + /** + * Returns the mapping driver implementation. + * + * @return \Doctrine\Common\Persistence\Mapping\Driver\MappingDriver + */ + abstract protected function getDriver(); + + /** + * Wakes up reflection after ClassMetadata gets unserialized from cache. + * + * @param ClassMetadata $class + * @param ReflectionService $reflService + * + * @return void + */ + abstract protected function wakeupReflection(ClassMetadata $class, ReflectionService $reflService); + + /** + * Initializes Reflection after ClassMetadata was constructed. + * + * @param ClassMetadata $class + * @param ReflectionService $reflService + * + * @return void + */ + abstract protected function initializeReflection(ClassMetadata $class, ReflectionService $reflService); + + /** + * Checks whether the class metadata is an entity. + * + * This method should return false for mapped superclasses or embedded classes. + * + * @param ClassMetadata $class + * + * @return boolean + */ + abstract protected function isEntity(ClassMetadata $class); + + /** + * Gets the class metadata descriptor for a class. + * + * @param string $className The name of the class. + * + * @return ClassMetadata + * + * @throws ReflectionException + * @throws MappingException + */ + public function getMetadataFor($className) + { + if (isset($this->loadedMetadata[$className])) { + return $this->loadedMetadata[$className]; + } + + // Check for namespace alias + if (strpos($className, ':') !== false) { + list($namespaceAlias, $simpleClassName) = explode(':', $className, 2); + + $realClassName = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName); + } else { + $realClassName = ClassUtils::getRealClass($className); + } + + if (isset($this->loadedMetadata[$realClassName])) { + // We do not have the alias name in the map, include it + return $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName]; + } + + $loadingException = null; + + try { + if ($this->cacheDriver) { + if (($cached = $this->cacheDriver->fetch($realClassName . $this->cacheSalt)) instanceof ClassMetadata) { + $this->loadedMetadata[$realClassName] = $cached; + + $this->wakeupReflection($cached, $this->getReflectionService()); + } else { + foreach ($this->loadMetadata($realClassName) as $loadedClassName) { + $this->cacheDriver->save( + $loadedClassName . $this->cacheSalt, + $this->loadedMetadata[$loadedClassName], + null + ); + } + } + } else { + $this->loadMetadata($realClassName); + } + } catch (MappingException $loadingException) { + if (! $fallbackMetadataResponse = $this->onNotFoundMetadata($realClassName)) { + throw $loadingException; + } + + $this->loadedMetadata[$realClassName] = $fallbackMetadataResponse; + } + + if ($className !== $realClassName) { + // We do not have the alias name in the map, include it + $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName]; + } + + return $this->loadedMetadata[$className]; + } + + /** + * Checks whether the factory has the metadata for a class loaded already. + * + * @param string $className + * + * @return boolean TRUE if the metadata of the class in question is already loaded, FALSE otherwise. + */ + public function hasMetadataFor($className) + { + return isset($this->loadedMetadata[$className]); + } + + /** + * Sets the metadata descriptor for a specific class. + * + * NOTE: This is only useful in very special cases, like when generating proxy classes. + * + * @param string $className + * @param ClassMetadata $class + * + * @return void + */ + public function setMetadataFor($className, $class) + { + $this->loadedMetadata[$className] = $class; + } + + /** + * Gets an array of parent classes for the given entity class. + * + * @param string $name + * + * @return array + */ + protected function getParentClasses($name) + { + // Collect parent classes, ignoring transient (not-mapped) classes. + $parentClasses = []; + foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) { + if ( ! $this->getDriver()->isTransient($parentClass)) { + $parentClasses[] = $parentClass; + } + } + return $parentClasses; + } + + /** + * Loads the metadata of the class in question and all it's ancestors whose metadata + * is still not loaded. + * + * Important: The class $name does not necesarily exist at this point here. + * Scenarios in a code-generation setup might have access to XML/YAML + * Mapping files without the actual PHP code existing here. That is why the + * {@see Doctrine\Common\Persistence\Mapping\ReflectionService} interface + * should be used for reflection. + * + * @param string $name The name of the class for which the metadata should get loaded. + * + * @return array + */ + protected function loadMetadata($name) + { + if ( ! $this->initialized) { + $this->initialize(); + } + + $loaded = []; + + $parentClasses = $this->getParentClasses($name); + $parentClasses[] = $name; + + // Move down the hierarchy of parent classes, starting from the topmost class + $parent = null; + $rootEntityFound = false; + $visited = []; + $reflService = $this->getReflectionService(); + foreach ($parentClasses as $className) { + if (isset($this->loadedMetadata[$className])) { + $parent = $this->loadedMetadata[$className]; + if ($this->isEntity($parent)) { + $rootEntityFound = true; + array_unshift($visited, $className); + } + continue; + } + + $class = $this->newClassMetadataInstance($className); + $this->initializeReflection($class, $reflService); + + $this->doLoadMetadata($class, $parent, $rootEntityFound, $visited); + + $this->loadedMetadata[$className] = $class; + + $parent = $class; + + if ($this->isEntity($class)) { + $rootEntityFound = true; + array_unshift($visited, $className); + } + + $this->wakeupReflection($class, $reflService); + + $loaded[] = $className; + } + + return $loaded; + } + + /** + * Provides a fallback hook for loading metadata when loading failed due to reflection/mapping exceptions + * + * Override this method to implement a fallback strategy for failed metadata loading + * + * @param string $className + * + * @return \Doctrine\Common\Persistence\Mapping\ClassMetadata|null + */ + protected function onNotFoundMetadata($className) + { + return null; + } + + /** + * Actually loads the metadata from the underlying metadata. + * + * @param ClassMetadata $class + * @param ClassMetadata|null $parent + * @param bool $rootEntityFound + * @param array $nonSuperclassParents All parent class names + * that are not marked as mapped superclasses. + * + * @return void + */ + abstract protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents); + + /** + * Creates a new ClassMetadata instance for the given class name. + * + * @param string $className + * + * @return ClassMetadata + */ + abstract protected function newClassMetadataInstance($className); + + /** + * {@inheritDoc} + */ + public function isTransient($class) + { + if ( ! $this->initialized) { + $this->initialize(); + } + + // Check for namespace alias + if (strpos($class, ':') !== false) { + list($namespaceAlias, $simpleClassName) = explode(':', $class, 2); + $class = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName); + } + + return $this->getDriver()->isTransient($class); + } + + /** + * Sets the reflectionService. + * + * @param ReflectionService $reflectionService + * + * @return void + */ + public function setReflectionService(ReflectionService $reflectionService) + { + $this->reflectionService = $reflectionService; + } + + /** + * Gets the reflection service associated with this metadata factory. + * + * @return ReflectionService + */ + public function getReflectionService() + { + if ($this->reflectionService === null) { + $this->reflectionService = new RuntimeReflectionService(); + } + return $this->reflectionService; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadata.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadata.php new file mode 100644 index 00000000..b8445f1b --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadata.php @@ -0,0 +1,174 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping; + +/** + * Contract for a Doctrine persistence layer ClassMetadata class to implement. + * + * @link www.doctrine-project.org + * @since 2.1 + * @author Benjamin Eberlei + * @author Jonathan Wage + */ +interface ClassMetadata +{ + /** + * Gets the fully-qualified class name of this persistent class. + * + * @return string + */ + public function getName(); + + /** + * Gets the mapped identifier field name. + * + * The returned structure is an array of the identifier field names. + * + * @return array + */ + public function getIdentifier(); + + /** + * Gets the ReflectionClass instance for this mapped class. + * + * @return \ReflectionClass + */ + public function getReflectionClass(); + + /** + * Checks if the given field name is a mapped identifier for this class. + * + * @param string $fieldName + * + * @return boolean + */ + public function isIdentifier($fieldName); + + /** + * Checks if the given field is a mapped property for this class. + * + * @param string $fieldName + * + * @return boolean + */ + public function hasField($fieldName); + + /** + * Checks if the given field is a mapped association for this class. + * + * @param string $fieldName + * + * @return boolean + */ + public function hasAssociation($fieldName); + + /** + * Checks if the given field is a mapped single valued association for this class. + * + * @param string $fieldName + * + * @return boolean + */ + public function isSingleValuedAssociation($fieldName); + + /** + * Checks if the given field is a mapped collection valued association for this class. + * + * @param string $fieldName + * + * @return boolean + */ + public function isCollectionValuedAssociation($fieldName); + + /** + * A numerically indexed list of field names of this persistent class. + * + * This array includes identifier fields if present on this class. + * + * @return array + */ + public function getFieldNames(); + + /** + * Returns an array of identifier field names numerically indexed. + * + * @return array + */ + public function getIdentifierFieldNames(); + + /** + * Returns a numerically indexed list of association names of this persistent class. + * + * This array includes identifier associations if present on this class. + * + * @return array + */ + public function getAssociationNames(); + + /** + * Returns a type name of this field. + * + * This type names can be implementation specific but should at least include the php types: + * integer, string, boolean, float/double, datetime. + * + * @param string $fieldName + * + * @return string + */ + public function getTypeOfField($fieldName); + + /** + * Returns the target class name of the given association. + * + * @param string $assocName + * + * @return string + */ + public function getAssociationTargetClass($assocName); + + /** + * Checks if the association is the inverse side of a bidirectional association. + * + * @param string $assocName + * + * @return boolean + */ + public function isAssociationInverseSide($assocName); + + /** + * Returns the target field of the owning side of the association. + * + * @param string $assocName + * + * @return string + */ + public function getAssociationMappedByTargetField($assocName); + + /** + * Returns the identifier of this object as an array with field name as key. + * + * Has to return an empty array if no identifier isset. + * + * @param object $object + * + * @return array + */ + public function getIdentifierValues($object); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadataFactory.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadataFactory.php new file mode 100644 index 00000000..3d828811 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadataFactory.php @@ -0,0 +1,76 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping; + +/** + * Contract for a Doctrine persistence layer ClassMetadata class to implement. + * + * @link www.doctrine-project.org + * @since 2.1 + * @author Benjamin Eberlei + * @author Jonathan Wage + */ +interface ClassMetadataFactory +{ + /** + * Forces the factory to load the metadata of all classes known to the underlying + * mapping driver. + * + * @return array The ClassMetadata instances of all mapped classes. + */ + public function getAllMetadata(); + + /** + * Gets the class metadata descriptor for a class. + * + * @param string $className The name of the class. + * + * @return ClassMetadata + */ + public function getMetadataFor($className); + + /** + * Checks whether the factory has the metadata for a class loaded already. + * + * @param string $className + * + * @return boolean TRUE if the metadata of the class in question is already loaded, FALSE otherwise. + */ + public function hasMetadataFor($className); + + /** + * Sets the metadata descriptor for a specific class. + * + * @param string $className + * + * @param ClassMetadata $class + */ + public function setMetadataFor($className, $class); + + /** + * Returns whether the class with the specified name should have its metadata loaded. + * This is only the case if it is either mapped directly or as a MappedSuperclass. + * + * @param string $className + * + * @return boolean + */ + public function isTransient($className); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/AnnotationDriver.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/AnnotationDriver.php new file mode 100644 index 00000000..deb82c0c --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/AnnotationDriver.php @@ -0,0 +1,256 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Annotations\AnnotationReader; +use Doctrine\Common\Persistence\Mapping\MappingException; + +/** + * The AnnotationDriver reads the mapping metadata from docblock annotations. + * + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan H. Wage + * @author Roman Borschel + */ +abstract class AnnotationDriver implements MappingDriver +{ + /** + * The AnnotationReader. + * + * @var AnnotationReader + */ + protected $reader; + + /** + * The paths where to look for mapping files. + * + * @var array + */ + protected $paths = []; + + /** + * The paths excluded from path where to look for mapping files. + * + * @var array + */ + protected $excludePaths = []; + + /** + * The file extension of mapping documents. + * + * @var string + */ + protected $fileExtension = '.php'; + + /** + * Cache for AnnotationDriver#getAllClassNames(). + * + * @var array|null + */ + protected $classNames; + + /** + * Name of the entity annotations as keys. + * + * @var array + */ + protected $entityAnnotationClasses = []; + + /** + * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading + * docblock annotations. + * + * @param AnnotationReader $reader The AnnotationReader to use, duck-typed. + * @param string|array|null $paths One or multiple paths where mapping classes can be found. + */ + public function __construct($reader, $paths = null) + { + $this->reader = $reader; + if ($paths) { + $this->addPaths((array) $paths); + } + } + + /** + * Appends lookup paths to metadata driver. + * + * @param array $paths + * + * @return void + */ + public function addPaths(array $paths) + { + $this->paths = array_unique(array_merge($this->paths, $paths)); + } + + /** + * Retrieves the defined metadata lookup paths. + * + * @return array + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Append exclude lookup paths to metadata driver. + * + * @param array $paths + */ + public function addExcludePaths(array $paths) + { + $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths)); + } + + /** + * Retrieve the defined metadata lookup exclude paths. + * + * @return array + */ + public function getExcludePaths() + { + return $this->excludePaths; + } + + /** + * Retrieve the current annotation reader + * + * @return AnnotationReader + */ + public function getReader() + { + return $this->reader; + } + + /** + * Gets the file extension used to look for mapping files under. + * + * @return string + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Sets the file extension used to look for mapping files under. + * + * @param string $fileExtension The file extension to set. + * + * @return void + */ + public function setFileExtension($fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * Returns whether the class with the specified name is transient. Only non-transient + * classes, that is entities and mapped superclasses, should have their metadata loaded. + * + * A class is non-transient if it is annotated with an annotation + * from the {@see AnnotationDriver::entityAnnotationClasses}. + * + * @param string $className + * + * @return boolean + */ + public function isTransient($className) + { + $classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className)); + + foreach ($classAnnotations as $annot) { + if (isset($this->entityAnnotationClasses[get_class($annot)])) { + return false; + } + } + return true; + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames() + { + if ($this->classNames !== null) { + return $this->classNames; + } + + if (!$this->paths) { + throw MappingException::pathRequired(); + } + + $classes = []; + $includedFiles = []; + + foreach ($this->paths as $path) { + if ( ! is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new \RegexIterator( + new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS), + \RecursiveIteratorIterator::LEAVES_ONLY + ), + '/^.+' . preg_quote($this->fileExtension) . '$/i', + \RecursiveRegexIterator::GET_MATCH + ); + + foreach ($iterator as $file) { + $sourceFile = $file[0]; + + if ( ! preg_match('(^phar:)i', $sourceFile)) { + $sourceFile = realpath($sourceFile); + } + + foreach ($this->excludePaths as $excludePath) { + $exclude = str_replace('\\', '/', realpath($excludePath)); + $current = str_replace('\\', '/', $sourceFile); + + if (strpos($current, $exclude) !== false) { + continue 2; + } + } + + require_once $sourceFile; + + $includedFiles[] = $sourceFile; + } + } + + $declared = get_declared_classes(); + + foreach ($declared as $className) { + $rc = new \ReflectionClass($className); + $sourceFile = $rc->getFileName(); + if (in_array($sourceFile, $includedFiles) && ! $this->isTransient($className)) { + $classes[] = $className; + } + } + + $this->classNames = $classes; + + return $classes; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/DefaultFileLocator.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/DefaultFileLocator.php new file mode 100644 index 00000000..6ed7f6d0 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/DefaultFileLocator.php @@ -0,0 +1,173 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Persistence\Mapping\MappingException; + +/** + * Locates the file that contains the metadata information for a given class name. + * + * This behavior is independent of the actual content of the file. It just detects + * the file which is responsible for the given class name. + * + * @author Benjamin Eberlei + * @author Johannes M. Schmitt + */ +class DefaultFileLocator implements FileLocator +{ + /** + * The paths where to look for mapping files. + * + * @var array + */ + protected $paths = []; + + /** + * The file extension of mapping documents. + * + * @var string|null + */ + protected $fileExtension; + + /** + * Initializes a new FileDriver that looks in the given path(s) for mapping + * documents and operates in the specified operating mode. + * + * @param string|array $paths One or multiple paths where mapping documents can be found. + * @param string|null $fileExtension The file extension of mapping documents, usually prefixed with a dot. + */ + public function __construct($paths, $fileExtension = null) + { + $this->addPaths((array) $paths); + $this->fileExtension = $fileExtension; + } + + /** + * Appends lookup paths to metadata driver. + * + * @param array $paths + * + * @return void + */ + public function addPaths(array $paths) + { + $this->paths = array_unique(array_merge($this->paths, $paths)); + } + + /** + * Retrieves the defined metadata lookup paths. + * + * @return array + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Gets the file extension used to look for mapping files under. + * + * @return string|null + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Sets the file extension used to look for mapping files under. + * + * @param string|null $fileExtension The file extension to set. + * + * @return void + */ + public function setFileExtension($fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * {@inheritDoc} + */ + public function findMappingFile($className) + { + $fileName = str_replace('\\', '.', $className) . $this->fileExtension; + + // Check whether file exists + foreach ($this->paths as $path) { + if (is_file($path . DIRECTORY_SEPARATOR . $fileName)) { + return $path . DIRECTORY_SEPARATOR . $fileName; + } + } + + throw MappingException::mappingFileNotFound($className, $fileName); + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames($globalBasename) + { + $classes = []; + + if ($this->paths) { + foreach ($this->paths as $path) { + if ( ! is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + $fileName = $file->getBasename($this->fileExtension); + + if ($fileName == $file->getBasename() || $fileName == $globalBasename) { + continue; + } + + // NOTE: All files found here means classes are not transient! + $classes[] = str_replace('.', '\\', $fileName); + } + } + } + + return $classes; + } + + /** + * {@inheritDoc} + */ + public function fileExists($className) + { + $fileName = str_replace('\\', '.', $className) . $this->fileExtension; + + // Check whether file exists + foreach ((array) $this->paths as $path) { + if (is_file($path . DIRECTORY_SEPARATOR . $fileName)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileDriver.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileDriver.php new file mode 100644 index 00000000..f17b263c --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileDriver.php @@ -0,0 +1,216 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Persistence\Mapping\MappingException; + +/** + * Base driver for file-based metadata drivers. + * + * A file driver operates in a mode where it loads the mapping files of individual + * classes on demand. This requires the user to adhere to the convention of 1 mapping + * file per class and the file names of the mapping files must correspond to the full + * class name, including namespace, with the namespace delimiters '\', replaced by dots '.'. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan H. Wage + * @author Roman Borschel + */ +abstract class FileDriver implements MappingDriver +{ + /** + * @var FileLocator + */ + protected $locator; + + /** + * @var array|null + */ + protected $classCache; + + /** + * @var string|null + */ + protected $globalBasename; + + /** + * Initializes a new FileDriver that looks in the given path(s) for mapping + * documents and operates in the specified operating mode. + * + * @param string|array|FileLocator $locator A FileLocator or one/multiple paths + * where mapping documents can be found. + * @param string|null $fileExtension + */ + public function __construct($locator, $fileExtension = null) + { + if ($locator instanceof FileLocator) { + $this->locator = $locator; + } else { + $this->locator = new DefaultFileLocator((array)$locator, $fileExtension); + } + } + + /** + * Sets the global basename. + * + * @param string $file + * + * @return void + */ + public function setGlobalBasename($file) + { + $this->globalBasename = $file; + } + + /** + * Retrieves the global basename. + * + * @return string|null + */ + public function getGlobalBasename() + { + return $this->globalBasename; + } + + /** + * Gets the element of schema meta data for the class from the mapping file. + * This will lazily load the mapping file if it is not loaded yet. + * + * @param string $className + * + * @return array The element of schema meta data. + * + * @throws MappingException + */ + public function getElement($className) + { + if ($this->classCache === null) { + $this->initialize(); + } + + if (isset($this->classCache[$className])) { + return $this->classCache[$className]; + } + + $result = $this->loadMappingFile($this->locator->findMappingFile($className)); + if (!isset($result[$className])) { + throw MappingException::invalidMappingFile($className, str_replace('\\', '.', $className) . $this->locator->getFileExtension()); + } + + $this->classCache[$className] = $result[$className]; + + return $result[$className]; + } + + /** + * {@inheritDoc} + */ + public function isTransient($className) + { + if ($this->classCache === null) { + $this->initialize(); + } + + if (isset($this->classCache[$className])) { + return false; + } + + return !$this->locator->fileExists($className); + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames() + { + if ($this->classCache === null) { + $this->initialize(); + } + + if (! $this->classCache) { + return (array) $this->locator->getAllClassNames($this->globalBasename); + } + + return array_merge( + array_keys($this->classCache), + (array) $this->locator->getAllClassNames($this->globalBasename) + ); + } + + /** + * Loads a mapping file with the given name and returns a map + * from class/entity names to their corresponding file driver elements. + * + * @param string $file The mapping file to load. + * + * @return array + */ + abstract protected function loadMappingFile($file); + + /** + * Initializes the class cache from all the global files. + * + * Using this feature adds a substantial performance hit to file drivers as + * more metadata has to be loaded into memory than might actually be + * necessary. This may not be relevant to scenarios where caching of + * metadata is in place, however hits very hard in scenarios where no + * caching is used. + * + * @return void + */ + protected function initialize() + { + $this->classCache = []; + if (null !== $this->globalBasename) { + foreach ($this->locator->getPaths() as $path) { + $file = $path.'/'.$this->globalBasename.$this->locator->getFileExtension(); + if (is_file($file)) { + $this->classCache = array_merge( + $this->classCache, + $this->loadMappingFile($file) + ); + } + } + } + } + + /** + * Retrieves the locator used to discover mapping files by className. + * + * @return FileLocator + */ + public function getLocator() + { + return $this->locator; + } + + /** + * Sets the locator used to discover mapping files by className. + * + * @param FileLocator $locator + */ + public function setLocator(FileLocator $locator) + { + $this->locator = $locator; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileLocator.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileLocator.php new file mode 100644 index 00000000..f622856d --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileLocator.php @@ -0,0 +1,73 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +/** + * Locates the file that contains the metadata information for a given class name. + * + * This behavior is independent of the actual content of the file. It just detects + * the file which is responsible for the given class name. + * + * @author Benjamin Eberlei + * @author Johannes M. Schmitt + */ +interface FileLocator +{ + /** + * Locates mapping file for the given class name. + * + * @param string $className + * + * @return string + */ + public function findMappingFile($className); + + /** + * Gets all class names that are found with this file locator. + * + * @param string $globalBasename Passed to allow excluding the basename. + * + * @return array + */ + public function getAllClassNames($globalBasename); + + /** + * Checks if a file can be found for this class name. + * + * @param string $className + * + * @return bool + */ + public function fileExists($className); + + /** + * Gets all the paths that this file locator looks for mapping files. + * + * @return array + */ + public function getPaths(); + + /** + * Gets the file extension that mapping files are suffixed with. + * + * @return string + */ + public function getFileExtension(); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriver.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriver.php new file mode 100644 index 00000000..b5d7ec03 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriver.php @@ -0,0 +1,58 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; + +/** + * Contract for metadata drivers. + * + * @since 2.2 + * @author Jonathan H. Wage + */ +interface MappingDriver +{ + /** + * Loads the metadata for the specified class into the provided container. + * + * @param string $className + * @param ClassMetadata $metadata + * + * @return void + */ + public function loadMetadataForClass($className, ClassMetadata $metadata); + + /** + * Gets the names of all mapped classes known to this driver. + * + * @return array The names of all mapped classes known to this driver. + */ + public function getAllClassNames(); + + /** + * Returns whether the class with the specified name should have its metadata loaded. + * This is only the case if it is either mapped as an Entity or a MappedSuperclass. + * + * @param string $className + * + * @return boolean + */ + public function isTransient($className); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriverChain.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriverChain.php new file mode 100644 index 00000000..50d9785b --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriverChain.php @@ -0,0 +1,165 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\Common\Persistence\Mapping\MappingException; + +/** + * The DriverChain allows you to add multiple other mapping drivers for + * certain namespaces. + * + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class MappingDriverChain implements MappingDriver +{ + /** + * The default driver. + * + * @var MappingDriver|null + */ + private $defaultDriver; + + /** + * @var array + */ + private $drivers = []; + + /** + * Gets the default driver. + * + * @return MappingDriver|null + */ + public function getDefaultDriver() + { + return $this->defaultDriver; + } + + /** + * Set the default driver. + * + * @param MappingDriver $driver + * + * @return void + */ + public function setDefaultDriver(MappingDriver $driver) + { + $this->defaultDriver = $driver; + } + + /** + * Adds a nested driver. + * + * @param MappingDriver $nestedDriver + * @param string $namespace + * + * @return void + */ + public function addDriver(MappingDriver $nestedDriver, $namespace) + { + $this->drivers[$namespace] = $nestedDriver; + } + + /** + * Gets the array of nested drivers. + * + * @return array $drivers + */ + public function getDrivers() + { + return $this->drivers; + } + + /** + * {@inheritDoc} + */ + public function loadMetadataForClass($className, ClassMetadata $metadata) + { + /* @var $driver MappingDriver */ + foreach ($this->drivers as $namespace => $driver) { + if (strpos($className, $namespace) === 0) { + $driver->loadMetadataForClass($className, $metadata); + return; + } + } + + if (null !== $this->defaultDriver) { + $this->defaultDriver->loadMetadataForClass($className, $metadata); + return; + } + + throw MappingException::classNotFoundInNamespaces($className, array_keys($this->drivers)); + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames() + { + $classNames = []; + $driverClasses = []; + + /* @var $driver MappingDriver */ + foreach ($this->drivers AS $namespace => $driver) { + $oid = spl_object_hash($driver); + + if (!isset($driverClasses[$oid])) { + $driverClasses[$oid] = $driver->getAllClassNames(); + } + + foreach ($driverClasses[$oid] AS $className) { + if (strpos($className, $namespace) === 0) { + $classNames[$className] = true; + } + } + } + + if (null !== $this->defaultDriver) { + foreach ($this->defaultDriver->getAllClassNames() as $className) { + $classNames[$className] = true; + } + } + + return array_keys($classNames); + } + + /** + * {@inheritDoc} + */ + public function isTransient($className) + { + /* @var $driver MappingDriver */ + foreach ($this->drivers AS $namespace => $driver) { + if (strpos($className, $namespace) === 0) { + return $driver->isTransient($className); + } + } + + if ($this->defaultDriver !== null) { + return $this->defaultDriver->isTransient($className); + } + + return true; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/PHPDriver.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/PHPDriver.php new file mode 100644 index 00000000..efffa6aa --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/PHPDriver.php @@ -0,0 +1,70 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; + +/** + * The PHPDriver includes php files which just populate ClassMetadataInfo + * instances with plain PHP code. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class PHPDriver extends FileDriver +{ + /** + * @var ClassMetadata + */ + protected $metadata; + + /** + * {@inheritDoc} + */ + public function __construct($locator, $fileExtension = null) + { + parent::__construct($locator, '.php'); + } + + /** + * {@inheritDoc} + */ + public function loadMetadataForClass($className, ClassMetadata $metadata) + { + $this->metadata = $metadata; + + $this->loadMappingFile($this->locator->findMappingFile($className)); + } + + /** + * {@inheritDoc} + */ + protected function loadMappingFile($file) + { + $metadata = $this->metadata; + include $file; + + return [$metadata->getName() => $metadata]; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/StaticPHPDriver.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/StaticPHPDriver.php new file mode 100644 index 00000000..8e37e5a7 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/StaticPHPDriver.php @@ -0,0 +1,142 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\Common\Persistence\Mapping\MappingException; + +/** + * The StaticPHPDriver calls a static loadMetadata() method on your entity + * classes where you can manually populate the ClassMetadata instance. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class StaticPHPDriver implements MappingDriver +{ + /** + * Paths of entity directories. + * + * @var array + */ + private $paths = []; + + /** + * Map of all class names. + * + * @var array + */ + private $classNames; + + /** + * Constructor. + * + * @param array|string $paths + */ + public function __construct($paths) + { + $this->addPaths((array) $paths); + } + + /** + * Adds paths. + * + * @param array $paths + * + * @return void + */ + public function addPaths(array $paths) + { + $this->paths = array_unique(array_merge($this->paths, $paths)); + } + + /** + * {@inheritdoc} + */ + public function loadMetadataForClass($className, ClassMetadata $metadata) + { + $className::loadMetadata($metadata); + } + + /** + * {@inheritDoc} + * @todo Same code exists in AnnotationDriver, should we re-use it somehow or not worry about it? + */ + public function getAllClassNames() + { + if ($this->classNames !== null) { + return $this->classNames; + } + + if (!$this->paths) { + throw MappingException::pathRequired(); + } + + $classes = []; + $includedFiles = []; + + foreach ($this->paths as $path) { + if (!is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + if ($file->getBasename('.php') == $file->getBasename()) { + continue; + } + + $sourceFile = realpath($file->getPathName()); + require_once $sourceFile; + $includedFiles[] = $sourceFile; + } + } + + $declared = get_declared_classes(); + + foreach ($declared as $className) { + $rc = new \ReflectionClass($className); + $sourceFile = $rc->getFileName(); + if (in_array($sourceFile, $includedFiles) && !$this->isTransient($className)) { + $classes[] = $className; + } + } + + $this->classNames = $classes; + + return $classes; + } + + /** + * {@inheritdoc} + */ + public function isTransient($className) + { + return ! method_exists($className, 'loadMetadata'); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/SymfonyFileLocator.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/SymfonyFileLocator.php new file mode 100644 index 00000000..4588cfec --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/SymfonyFileLocator.php @@ -0,0 +1,239 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Persistence\Mapping\MappingException; + +/** + * The Symfony File Locator makes a simplifying assumptions compared + * to the DefaultFileLocator. By assuming paths only contain entities of a certain + * namespace the mapping files consists of the short classname only. + * + * @author Fabien Potencier + * @author Benjamin Eberlei + * @license MIT + */ +class SymfonyFileLocator implements FileLocator +{ + /** + * The paths where to look for mapping files. + * + * @var array + */ + protected $paths = []; + + /** + * A map of mapping directory path to namespace prefix used to expand class shortnames. + * + * @var array + */ + protected $prefixes = []; + + /** + * File extension that is searched for. + * + * @var string|null + */ + protected $fileExtension; + + /** + * Represents PHP namespace delimiters when looking for files + * + * @var string + */ + private $nsSeparator; + + /** + * Constructor. + * + * @param array $prefixes + * @param string|null $fileExtension + * @param string $nsSeparator String which would be used when converting FQCN to filename and vice versa. Should not be empty + */ + public function __construct(array $prefixes, $fileExtension = null, $nsSeparator = '.') + { + $this->addNamespacePrefixes($prefixes); + $this->fileExtension = $fileExtension; + + if (empty($nsSeparator)) { + throw new \InvalidArgumentException('Namespace separator should not be empty'); + } + + $this->nsSeparator = (string) $nsSeparator; + } + + /** + * Adds Namespace Prefixes. + * + * @param array $prefixes + * + * @return void + */ + public function addNamespacePrefixes(array $prefixes) + { + $this->prefixes = array_merge($this->prefixes, $prefixes); + $this->paths = array_merge($this->paths, array_keys($prefixes)); + } + + /** + * Gets Namespace Prefixes. + * + * @return array + */ + public function getNamespacePrefixes() + { + return $this->prefixes; + } + + /** + * {@inheritDoc} + */ + public function getPaths() + { + return $this->paths; + } + + /** + * {@inheritDoc} + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Sets the file extension used to look for mapping files under. + * + * @param string $fileExtension The file extension to set. + * + * @return void + */ + public function setFileExtension($fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * {@inheritDoc} + */ + public function fileExists($className) + { + $defaultFileName = str_replace('\\', $this->nsSeparator, $className).$this->fileExtension; + foreach ($this->paths as $path) { + if (!isset($this->prefixes[$path])) { + // global namespace class + if (is_file($path.DIRECTORY_SEPARATOR.$defaultFileName)) { + return true; + } + + continue; + } + + $prefix = $this->prefixes[$path]; + + if (0 !== strpos($className, $prefix.'\\')) { + continue; + } + + $filename = $path.'/'.strtr(substr($className, strlen($prefix)+1), '\\', $this->nsSeparator).$this->fileExtension; + if (is_file($filename)) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames($globalBasename = null) + { + $classes = []; + + if ($this->paths) { + foreach ((array) $this->paths as $path) { + if (!is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + $fileName = $file->getBasename($this->fileExtension); + + if ($fileName == $file->getBasename() || $fileName == $globalBasename) { + continue; + } + + // NOTE: All files found here means classes are not transient! + if (isset($this->prefixes[$path])) { + + // Calculate namespace suffix for given prefix as a relative path from basepath to file path + $nsSuffix = strtr( + substr(realpath($file->getPath()), strlen(realpath($path))), + $this->nsSeparator, + '\\' + ); + + $classes[] = $this->prefixes[$path] . str_replace(DIRECTORY_SEPARATOR, '\\', $nsSuffix) . '\\' .str_replace($this->nsSeparator, '\\', $fileName); + } else { + $classes[] = str_replace($this->nsSeparator, '\\', $fileName); + } + } + } + } + + return $classes; + } + + /** + * {@inheritDoc} + */ + public function findMappingFile($className) + { + $defaultFileName = str_replace('\\', $this->nsSeparator, $className).$this->fileExtension; + foreach ($this->paths as $path) { + if (!isset($this->prefixes[$path])) { + if (is_file($path.DIRECTORY_SEPARATOR.$defaultFileName)) { + return $path.DIRECTORY_SEPARATOR.$defaultFileName; + } + + continue; + } + + $prefix = $this->prefixes[$path]; + + if (0 !== strpos($className, $prefix.'\\')) { + continue; + } + + $filename = $path.'/'.strtr(substr($className, strlen($prefix)+1), '\\', $this->nsSeparator ).$this->fileExtension; + if (is_file($filename)) { + return $filename; + } + } + + throw MappingException::mappingFileNotFound($className, substr($className, strrpos($className, '\\') + 1).$this->fileExtension); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/MappingException.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/MappingException.php new file mode 100644 index 00000000..6e97485e --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/MappingException.php @@ -0,0 +1,98 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping; + +/** + * A MappingException indicates that something is wrong with the mapping setup. + * + * @since 2.2 + */ +class MappingException extends \Exception +{ + /** + * @param string $className + * @param array $namespaces + * + * @return self + */ + public static function classNotFoundInNamespaces($className, $namespaces) + { + return new self("The class '" . $className . "' was not found in the ". + "chain configured namespaces " . implode(", ", $namespaces)); + } + + /** + * @return self + */ + public static function pathRequired() + { + return new self("Specifying the paths to your entities is required ". + "in the AnnotationDriver to retrieve all class names."); + } + + /** + * @param string|null $path + * + * @return self + */ + public static function fileMappingDriversRequireConfiguredDirectoryPath($path = null) + { + if ( ! empty($path)) { + $path = '[' . $path . ']'; + } + + return new self( + 'File mapping drivers must have a valid directory path, ' . + 'however the given path ' . $path . ' seems to be incorrect!' + ); + } + + /** + * @param string $entityName + * @param string $fileName + * + * @return self + */ + public static function mappingFileNotFound($entityName, $fileName) + { + return new self("No mapping file found named '$fileName' for class '$entityName'."); + } + + /** + * @param string $entityName + * @param string $fileName + * + * @return self + */ + public static function invalidMappingFile($entityName, $fileName) + { + return new self("Invalid mapping file '$fileName' for class '$entityName'."); + } + + /** + * @param string $className + * + * @return self + */ + public static function nonExistingClass($className) + { + return new self("Class '$className' does not exist"); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ReflectionService.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ReflectionService.php new file mode 100644 index 00000000..0088ed5e --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ReflectionService.php @@ -0,0 +1,87 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping; + +/** + * Very simple reflection service abstraction. + * + * This is required inside metadata layers that may require either + * static or runtime reflection. + * + * @author Benjamin Eberlei + */ +interface ReflectionService +{ + /** + * Returns an array of the parent classes (not interfaces) for the given class. + * + * @param string $class + * + * @throws \Doctrine\Common\Persistence\Mapping\MappingException + * + * @return array + */ + public function getParentClasses($class); + + /** + * Returns the shortname of a class. + * + * @param string $class + * + * @return string + */ + public function getClassShortName($class); + + /** + * @param string $class + * + * @return string + */ + public function getClassNamespace($class); + + /** + * Returns a reflection class instance or null. + * + * @param string $class + * + * @return \ReflectionClass|null + */ + public function getClass($class); + + /** + * Returns an accessible property (setAccessible(true)) or null. + * + * @param string $class + * @param string $property + * + * @return \ReflectionProperty|null + */ + public function getAccessibleProperty($class, $property); + + /** + * Checks if the class have a public method with the given name. + * + * @param mixed $class + * @param mixed $method + * + * @return bool + */ + public function hasPublicMethod($class, $method); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/RuntimeReflectionService.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/RuntimeReflectionService.php new file mode 100644 index 00000000..4598d5af --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/RuntimeReflectionService.php @@ -0,0 +1,104 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping; + +use Doctrine\Common\Reflection\RuntimePublicReflectionProperty; +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; +use ReflectionProperty; + +/** + * PHP Runtime Reflection Service. + * + * @author Benjamin Eberlei + */ +class RuntimeReflectionService implements ReflectionService +{ + /** + * {@inheritDoc} + */ + public function getParentClasses($class) + { + if ( ! class_exists($class)) { + throw MappingException::nonExistingClass($class); + } + + return class_parents($class); + } + + /** + * {@inheritDoc} + */ + public function getClassShortName($class) + { + $reflectionClass = new ReflectionClass($class); + + return $reflectionClass->getShortName(); + } + + /** + * {@inheritDoc} + */ + public function getClassNamespace($class) + { + $reflectionClass = new ReflectionClass($class); + + return $reflectionClass->getNamespaceName(); + } + + /** + * {@inheritDoc} + */ + public function getClass($class) + { + return new ReflectionClass($class); + } + + /** + * {@inheritDoc} + */ + public function getAccessibleProperty($class, $property) + { + $reflectionProperty = new ReflectionProperty($class, $property); + + if ($reflectionProperty->isPublic()) { + $reflectionProperty = new RuntimePublicReflectionProperty($class, $property); + } + + $reflectionProperty->setAccessible(true); + + return $reflectionProperty; + } + + /** + * {@inheritDoc} + */ + public function hasPublicMethod($class, $method) + { + try { + $reflectionMethod = new ReflectionMethod($class, $method); + } catch (ReflectionException $e) { + return false; + } + + return $reflectionMethod->isPublic(); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/StaticReflectionService.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/StaticReflectionService.php new file mode 100644 index 00000000..7d017664 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/StaticReflectionService.php @@ -0,0 +1,83 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping; + +/** + * PHP Runtime Reflection Service. + * + * @author Benjamin Eberlei + */ +class StaticReflectionService implements ReflectionService +{ + /** + * {@inheritDoc} + */ + public function getParentClasses($class) + { + return []; + } + + /** + * {@inheritDoc} + */ + public function getClassShortName($className) + { + if (strpos($className, '\\') !== false) { + $className = substr($className, strrpos($className, "\\")+1); + } + return $className; + } + + /** + * {@inheritDoc} + */ + public function getClassNamespace($className) + { + $namespace = ''; + if (strpos($className, '\\') !== false) { + $namespace = strrev(substr( strrev($className), strpos(strrev($className), '\\')+1 )); + } + return $namespace; + } + + /** + * {@inheritDoc} + */ + public function getClass($class) + { + return null; + } + + /** + * {@inheritDoc} + */ + public function getAccessibleProperty($class, $property) + { + return null; + } + + /** + * {@inheritDoc} + */ + public function hasPublicMethod($class, $method) + { + return true; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManager.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManager.php new file mode 100644 index 00000000..02208c9d --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManager.php @@ -0,0 +1,169 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +/** + * Contract for a Doctrine persistence layer ObjectManager class to implement. + * + * @link www.doctrine-project.org + * @since 2.1 + * @author Benjamin Eberlei + * @author Jonathan Wage + */ +interface ObjectManager +{ + /** + * Finds an object by its identifier. + * + * This is just a convenient shortcut for getRepository($className)->find($id). + * + * @param string $className The class name of the object to find. + * @param mixed $id The identity of the object to find. + * + * @return object The found object. + */ + public function find($className, $id); + + /** + * Tells the ObjectManager to make an instance managed and persistent. + * + * The object will be entered into the database as a result of the flush operation. + * + * NOTE: The persist operation always considers objects that are not yet known to + * this ObjectManager as NEW. Do not pass detached objects to the persist operation. + * + * @param object $object The instance to make managed and persistent. + * + * @return void + */ + public function persist($object); + + /** + * Removes an object instance. + * + * A removed object will be removed from the database as a result of the flush operation. + * + * @param object $object The object instance to remove. + * + * @return void + */ + public function remove($object); + + /** + * Merges the state of a detached object into the persistence context + * of this ObjectManager and returns the managed copy of the object. + * The object passed to merge will not become associated/managed with this ObjectManager. + * + * @param object $object + * + * @return object + */ + public function merge($object); + + /** + * Clears the ObjectManager. All objects that are currently managed + * by this ObjectManager become detached. + * + * @param string|null $objectName if given, only objects of this type will get detached. + * + * @return void + */ + public function clear($objectName = null); + + /** + * Detaches an object from the ObjectManager, causing a managed object to + * become detached. Unflushed changes made to the object if any + * (including removal of the object), will not be synchronized to the database. + * Objects which previously referenced the detached object will continue to + * reference it. + * + * @param object $object The object to detach. + * + * @return void + */ + public function detach($object); + + /** + * Refreshes the persistent state of an object from the database, + * overriding any local changes that have not yet been persisted. + * + * @param object $object The object to refresh. + * + * @return void + */ + public function refresh($object); + + /** + * Flushes all changes to objects that have been queued up to now to the database. + * This effectively synchronizes the in-memory state of managed objects with the + * database. + * + * @return void + */ + public function flush(); + + /** + * Gets the repository for a class. + * + * @param string $className + * + * @return \Doctrine\Common\Persistence\ObjectRepository + */ + public function getRepository($className); + + /** + * Returns the ClassMetadata descriptor for a class. + * + * The class name must be the fully-qualified class name without a leading backslash + * (as it is returned by get_class($obj)). + * + * @param string $className + * + * @return \Doctrine\Common\Persistence\Mapping\ClassMetadata + */ + public function getClassMetadata($className); + + /** + * Gets the metadata factory used to gather the metadata of classes. + * + * @return \Doctrine\Common\Persistence\Mapping\ClassMetadataFactory + */ + public function getMetadataFactory(); + + /** + * Helper method to initialize a lazy loading proxy or persistent collection. + * + * This method is a no-op for other objects. + * + * @param object $obj + * + * @return void + */ + public function initializeObject($obj); + + /** + * Checks if the object is part of the current UnitOfWork and therefore managed. + * + * @param object $object + * + * @return bool + */ + public function contains($object); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerAware.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerAware.php new file mode 100644 index 00000000..9bc248a5 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerAware.php @@ -0,0 +1,51 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; + +/** + * Makes a Persistent Objects aware of its own object-manager. + * + * Using this interface the managing object manager and class metadata instances + * are injected into the persistent object after construction. This allows + * you to implement ActiveRecord functionality on top of the persistence-ignorance + * that Doctrine propagates. + * + * Word of Warning: This is a very powerful hook to change how you can work with your domain models. + * Using this hook will break the Single Responsibility Principle inside your Domain Objects + * and increase the coupling of database and objects. + * + * Every ObjectManager has to implement this functionality itself. + * + * @author Benjamin Eberlei + */ +interface ObjectManagerAware +{ + /** + * Injects responsible ObjectManager and the ClassMetadata into this persistent object. + * + * @param ObjectManager $objectManager + * @param ClassMetadata $classMetadata + * + * @return void + */ + public function injectObjectManager(ObjectManager $objectManager, ClassMetadata $classMetadata); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerDecorator.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerDecorator.php new file mode 100644 index 00000000..8946475d --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerDecorator.php @@ -0,0 +1,140 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +/** + * Base class to simplify ObjectManager decorators + * + * @license http://opensource.org/licenses/MIT MIT + * @link www.doctrine-project.org + * @since 2.4 + * @author Lars Strojny + */ +abstract class ObjectManagerDecorator implements ObjectManager +{ + /** + * @var ObjectManager + */ + protected $wrapped; + + /** + * {@inheritdoc} + */ + public function find($className, $id) + { + return $this->wrapped->find($className, $id); + } + + /** + * {@inheritdoc} + */ + public function persist($object) + { + return $this->wrapped->persist($object); + } + + /** + * {@inheritdoc} + */ + public function remove($object) + { + return $this->wrapped->remove($object); + } + + /** + * {@inheritdoc} + */ + public function merge($object) + { + return $this->wrapped->merge($object); + } + + /** + * {@inheritdoc} + */ + public function clear($objectName = null) + { + return $this->wrapped->clear($objectName); + } + + /** + * {@inheritdoc} + */ + public function detach($object) + { + return $this->wrapped->detach($object); + } + + /** + * {@inheritdoc} + */ + public function refresh($object) + { + return $this->wrapped->refresh($object); + } + + /** + * {@inheritdoc} + */ + public function flush() + { + return $this->wrapped->flush(); + } + + /** + * {@inheritdoc} + */ + public function getRepository($className) + { + return $this->wrapped->getRepository($className); + } + + /** + * {@inheritdoc} + */ + public function getClassMetadata($className) + { + return $this->wrapped->getClassMetadata($className); + } + + /** + * {@inheritdoc} + */ + public function getMetadataFactory() + { + return $this->wrapped->getMetadataFactory(); + } + + /** + * {@inheritdoc} + */ + public function initializeObject($obj) + { + return $this->wrapped->initializeObject($obj); + } + + /** + * {@inheritdoc} + */ + public function contains($object) + { + return $this->wrapped->contains($object); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectRepository.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectRepository.php new file mode 100644 index 00000000..fd968a3e --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectRepository.php @@ -0,0 +1,81 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +/** + * Contract for a Doctrine persistence layer ObjectRepository class to implement. + * + * @link www.doctrine-project.org + * @since 2.1 + * @author Benjamin Eberlei + * @author Jonathan Wage + */ +interface ObjectRepository +{ + /** + * Finds an object by its primary key / identifier. + * + * @param mixed $id The identifier. + * + * @return object|null The object. + */ + public function find($id); + + /** + * Finds all objects in the repository. + * + * @return array The objects. + */ + public function findAll(); + + /** + * Finds objects by a set of criteria. + * + * Optionally sorting and limiting details can be passed. An implementation may throw + * an UnexpectedValueException if certain values of the sorting or limiting details are + * not supported. + * + * @param array $criteria + * @param array|null $orderBy + * @param int|null $limit + * @param int|null $offset + * + * @return array The objects. + * + * @throws \UnexpectedValueException + */ + public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null); + + /** + * Finds a single object by a set of criteria. + * + * @param array $criteria The criteria. + * + * @return object|null The object. + */ + public function findOneBy(array $criteria); + + /** + * Returns the class name of the object managed by the repository. + * + * @return string + */ + public function getClassName(); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/PersistentObject.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/PersistentObject.php new file mode 100644 index 00000000..990642e7 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/PersistentObject.php @@ -0,0 +1,254 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Persistence\Mapping\ClassMetadata; + +/** + * PersistentObject base class that implements getter/setter methods for all mapped fields and associations + * by overriding __call. + * + * This class is a forward compatible implementation of the PersistentObject trait. + * + * Limitations: + * + * 1. All persistent objects have to be associated with a single ObjectManager, multiple + * ObjectManagers are not supported. You can set the ObjectManager with `PersistentObject#setObjectManager()`. + * 2. Setters and getters only work if a ClassMetadata instance was injected into the PersistentObject. + * This is either done on `postLoad` of an object or by accessing the global object manager. + * 3. There are no hooks for setters/getters. Just implement the method yourself instead of relying on __call(). + * 4. Slower than handcoded implementations: An average of 7 method calls per access to a field and 11 for an association. + * 5. Only the inverse side associations get autoset on the owning side as well. Setting objects on the owning side + * will not set the inverse side associations. + * + * @example + * + * PersistentObject::setObjectManager($em); + * + * class Foo extends PersistentObject + * { + * private $id; + * } + * + * $foo = new Foo(); + * $foo->getId(); // method exists through __call + * + * @author Benjamin Eberlei + */ +abstract class PersistentObject implements ObjectManagerAware +{ + /** + * @var ObjectManager|null + */ + private static $objectManager = null; + + /** + * @var ClassMetadata|null + */ + private $cm = null; + + /** + * Sets the object manager responsible for all persistent object base classes. + * + * @param ObjectManager|null $objectManager + * + * @return void + */ + static public function setObjectManager(ObjectManager $objectManager = null) + { + self::$objectManager = $objectManager; + } + + /** + * @return ObjectManager|null + */ + static public function getObjectManager() + { + return self::$objectManager; + } + + /** + * Injects the Doctrine Object Manager. + * + * @param ObjectManager $objectManager + * @param ClassMetadata $classMetadata + * + * @return void + * + * @throws \RuntimeException + */ + public function injectObjectManager(ObjectManager $objectManager, ClassMetadata $classMetadata) + { + if ($objectManager !== self::$objectManager) { + throw new \RuntimeException("Trying to use PersistentObject with different ObjectManager instances. " . + "Was PersistentObject::setObjectManager() called?"); + } + + $this->cm = $classMetadata; + } + + /** + * Sets a persistent fields value. + * + * @param string $field + * @param array $args + * + * @return void + * + * @throws \BadMethodCallException When no persistent field exists by that name. + * @throws \InvalidArgumentException When the wrong target object type is passed to an association. + */ + private function set($field, $args) + { + $this->initializeDoctrine(); + + if ($this->cm->hasField($field) && !$this->cm->isIdentifier($field)) { + $this->$field = $args[0]; + } else if ($this->cm->hasAssociation($field) && $this->cm->isSingleValuedAssociation($field)) { + $targetClass = $this->cm->getAssociationTargetClass($field); + if (!($args[0] instanceof $targetClass) && $args[0] !== null) { + throw new \InvalidArgumentException("Expected persistent object of type '".$targetClass."'"); + } + $this->$field = $args[0]; + $this->completeOwningSide($field, $targetClass, $args[0]); + } else { + throw new \BadMethodCallException("no field with name '".$field."' exists on '".$this->cm->getName()."'"); + } + } + + /** + * Gets a persistent field value. + * + * @param string $field + * + * @return mixed + * + * @throws \BadMethodCallException When no persistent field exists by that name. + */ + private function get($field) + { + $this->initializeDoctrine(); + + if ( $this->cm->hasField($field) || $this->cm->hasAssociation($field) ) { + return $this->$field; + } else { + throw new \BadMethodCallException("no field with name '".$field."' exists on '".$this->cm->getName()."'"); + } + } + + /** + * If this is an inverse side association, completes the owning side. + * + * @param string $field + * @param ClassMetadata $targetClass + * @param object $targetObject + * + * @return void + */ + private function completeOwningSide($field, $targetClass, $targetObject) + { + // add this object on the owning side as well, for obvious infinite recursion + // reasons this is only done when called on the inverse side. + if ($this->cm->isAssociationInverseSide($field)) { + $mappedByField = $this->cm->getAssociationMappedByTargetField($field); + $targetMetadata = self::$objectManager->getClassMetadata($targetClass); + + $setter = ($targetMetadata->isCollectionValuedAssociation($mappedByField) ? "add" : "set").$mappedByField; + $targetObject->$setter($this); + } + } + + /** + * Adds an object to a collection. + * + * @param string $field + * @param array $args + * + * @return void + * + * @throws \BadMethodCallException + * @throws \InvalidArgumentException + */ + private function add($field, $args) + { + $this->initializeDoctrine(); + + if ($this->cm->hasAssociation($field) && $this->cm->isCollectionValuedAssociation($field)) { + $targetClass = $this->cm->getAssociationTargetClass($field); + if (!($args[0] instanceof $targetClass)) { + throw new \InvalidArgumentException("Expected persistent object of type '".$targetClass."'"); + } + if (!($this->$field instanceof Collection)) { + $this->$field = new ArrayCollection($this->$field ?: []); + } + $this->$field->add($args[0]); + $this->completeOwningSide($field, $targetClass, $args[0]); + } else { + throw new \BadMethodCallException("There is no method add".$field."() on ".$this->cm->getName()); + } + } + + /** + * Initializes Doctrine Metadata for this class. + * + * @return void + * + * @throws \RuntimeException + */ + private function initializeDoctrine() + { + if ($this->cm !== null) { + return; + } + + if (!self::$objectManager) { + throw new \RuntimeException("No runtime object manager set. Call PersistentObject#setObjectManager()."); + } + + $this->cm = self::$objectManager->getClassMetadata(get_class($this)); + } + + /** + * Magic methods. + * + * @param string $method + * @param array $args + * + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $args) + { + $command = substr($method, 0, 3); + $field = lcfirst(substr($method, 3)); + if ($command == "set") { + $this->set($field, $args); + } else if ($command == "get") { + return $this->get($field); + } else if ($command == "add") { + $this->add($field, $args); + } else { + throw new \BadMethodCallException("There is no method ".$method." on ".$this->cm->getName()); + } + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Proxy.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Proxy.php new file mode 100644 index 00000000..3369eb94 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Proxy.php @@ -0,0 +1,59 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +/** + * Interface for proxy classes. + * + * @author Roman Borschel + * @since 2.2 + */ +interface Proxy +{ + /** + * Marker for Proxy class names. + * + * @var string + */ + const MARKER = '__CG__'; + + /** + * Length of the proxy marker. + * + * @var integer + */ + const MARKER_LENGTH = 6; + + /** + * Initializes this proxy if its not yet initialized. + * + * Acts as a no-op if already initialized. + * + * @return void + */ + public function __load(); + + /** + * Returns whether this proxy is initialized or not. + * + * @return bool + */ + public function __isInitialized(); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/PropertyChangedListener.php b/vendor/doctrine/common/lib/Doctrine/Common/PropertyChangedListener.php new file mode 100644 index 00000000..1a59cd4d --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/PropertyChangedListener.php @@ -0,0 +1,45 @@ +. + */ + +namespace Doctrine\Common; + +/** + * Contract for classes that are potential listeners of a NotifyPropertyChanged + * implementor. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +interface PropertyChangedListener +{ + /** + * Notifies the listener of a property change. + * + * @param object $sender The object on which the property changed. + * @param string $propertyName The name of the property that changed. + * @param mixed $oldValue The old value of the property that changed. + * @param mixed $newValue The new value of the property that changed. + * + * @return void + */ + function propertyChanged($sender, $propertyName, $oldValue, $newValue); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php new file mode 100644 index 00000000..7eb216bf --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php @@ -0,0 +1,248 @@ +. + */ + +namespace Doctrine\Common\Proxy; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory; +use Doctrine\Common\Proxy\Exception\InvalidArgumentException; +use Doctrine\Common\Proxy\Exception\OutOfBoundsException; +use Doctrine\Common\Util\ClassUtils; + +/** + * Abstract factory for proxy objects. + * + * @author Benjamin Eberlei + */ +abstract class AbstractProxyFactory +{ + /** + * Never autogenerate a proxy and rely that it was generated by some + * process before deployment. + * + * @var integer + */ + const AUTOGENERATE_NEVER = 0; + + /** + * Always generates a new proxy in every request. + * + * This is only sane during development. + * + * @var integer + */ + const AUTOGENERATE_ALWAYS = 1; + + /** + * Autogenerate the proxy class when the proxy file does not exist. + * + * This strategy causes a file exists call whenever any proxy is used the + * first time in a request. + * + * @var integer + */ + const AUTOGENERATE_FILE_NOT_EXISTS = 2; + + /** + * Generate the proxy classes using eval(). + * + * This strategy is only sane for development, and even then it gives me + * the creeps a little. + * + * @var integer + */ + const AUTOGENERATE_EVAL = 3; + + /** + * @var \Doctrine\Common\Persistence\Mapping\ClassMetadataFactory + */ + private $metadataFactory; + + /** + * @var \Doctrine\Common\Proxy\ProxyGenerator the proxy generator responsible for creating the proxy classes/files. + */ + private $proxyGenerator; + + /** + * @var bool Whether to automatically (re)generate proxy classes. + */ + private $autoGenerate; + + /** + * @var \Doctrine\Common\Proxy\ProxyDefinition[] + */ + private $definitions = []; + + /** + * @param \Doctrine\Common\Proxy\ProxyGenerator $proxyGenerator + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadataFactory $metadataFactory + * @param bool|int $autoGenerate + */ + public function __construct(ProxyGenerator $proxyGenerator, ClassMetadataFactory $metadataFactory, $autoGenerate) + { + $this->proxyGenerator = $proxyGenerator; + $this->metadataFactory = $metadataFactory; + $this->autoGenerate = (int)$autoGenerate; + } + + /** + * Gets a reference proxy instance for the entity of the given type and identified by + * the given identifier. + * + * @param string $className + * @param array $identifier + * + * @return \Doctrine\Common\Proxy\Proxy + * + * @throws \Doctrine\Common\Proxy\Exception\OutOfBoundsException + */ + public function getProxy($className, array $identifier) + { + $definition = isset($this->definitions[$className]) + ? $this->definitions[$className] + : $this->getProxyDefinition($className); + $fqcn = $definition->proxyClassName; + $proxy = new $fqcn($definition->initializer, $definition->cloner); + + foreach ($definition->identifierFields as $idField) { + if (! isset($identifier[$idField])) { + throw OutOfBoundsException::missingPrimaryKeyValue($className, $idField); + } + + $definition->reflectionFields[$idField]->setValue($proxy, $identifier[$idField]); + } + + return $proxy; + } + + /** + * Generates proxy classes for all given classes. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata[] $classes The classes (ClassMetadata instances) + * for which to generate proxies. + * @param string $proxyDir The target directory of the proxy classes. If not specified, the + * directory configured on the Configuration of the EntityManager used + * by this factory is used. + * @return int Number of generated proxies. + */ + public function generateProxyClasses(array $classes, $proxyDir = null) + { + $generated = 0; + + foreach ($classes as $class) { + if ($this->skipClass($class)) { + continue; + } + + $proxyFileName = $this->proxyGenerator->getProxyFileName($class->getName(), $proxyDir); + + $this->proxyGenerator->generateProxyClass($class, $proxyFileName); + + $generated += 1; + } + + return $generated; + } + + /** + * Reset initialization/cloning logic for an un-initialized proxy + * + * @param \Doctrine\Common\Proxy\Proxy $proxy + * + * @return \Doctrine\Common\Proxy\Proxy + * + * @throws \Doctrine\Common\Proxy\Exception\InvalidArgumentException + */ + public function resetUninitializedProxy(Proxy $proxy) + { + if ($proxy->__isInitialized()) { + throw InvalidArgumentException::unitializedProxyExpected($proxy); + } + + $className = ClassUtils::getClass($proxy); + $definition = isset($this->definitions[$className]) + ? $this->definitions[$className] + : $this->getProxyDefinition($className); + + $proxy->__setInitializer($definition->initializer); + $proxy->__setCloner($definition->cloner); + + return $proxy; + } + + /** + * Get a proxy definition for the given class name. + * + * @param string $className + * + * @return ProxyDefinition + */ + private function getProxyDefinition($className) + { + $classMetadata = $this->metadataFactory->getMetadataFor($className); + $className = $classMetadata->getName(); // aliases and case sensitivity + + $this->definitions[$className] = $this->createProxyDefinition($className); + $proxyClassName = $this->definitions[$className]->proxyClassName; + + if ( ! class_exists($proxyClassName, false)) { + $fileName = $this->proxyGenerator->getProxyFileName($className); + + switch ($this->autoGenerate) { + case self::AUTOGENERATE_NEVER: + require $fileName; + break; + + case self::AUTOGENERATE_FILE_NOT_EXISTS: + if ( ! file_exists($fileName)) { + $this->proxyGenerator->generateProxyClass($classMetadata, $fileName); + } + require $fileName; + break; + + case self::AUTOGENERATE_ALWAYS: + $this->proxyGenerator->generateProxyClass($classMetadata, $fileName); + require $fileName; + break; + + case self::AUTOGENERATE_EVAL: + $this->proxyGenerator->generateProxyClass($classMetadata, false); + break; + } + } + + return $this->definitions[$className]; + } + + /** + * Determine if this class should be skipped during proxy generation. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $metadata + * + * @return bool + */ + abstract protected function skipClass(ClassMetadata $metadata); + + /** + * @param string $className + * + * @return ProxyDefinition + */ + abstract protected function createProxyDefinition($className); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Autoloader.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Autoloader.php new file mode 100644 index 00000000..fae3348e --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Autoloader.php @@ -0,0 +1,96 @@ +. + */ + +namespace Doctrine\Common\Proxy; + +use Doctrine\Common\Proxy\Exception\InvalidArgumentException; + +/** + * Special Autoloader for Proxy classes, which are not PSR-0 compliant. + * + * @author Benjamin Eberlei + */ +class Autoloader +{ + /** + * Resolves proxy class name to a filename based on the following pattern. + * + * 1. Remove Proxy namespace from class name. + * 2. Remove namespace separators from remaining class name. + * 3. Return PHP filename from proxy-dir with the result from 2. + * + * @param string $proxyDir + * @param string $proxyNamespace + * @param string $className + * + * @return string + * + * @throws InvalidArgumentException + */ + public static function resolveFile($proxyDir, $proxyNamespace, $className) + { + if (0 !== strpos($className, $proxyNamespace)) { + throw InvalidArgumentException::notProxyClass($className, $proxyNamespace); + } + + // remove proxy namespace from class name + $classNameRelativeToProxyNamespace = substr($className, strlen($proxyNamespace)); + + // remove namespace separators from remaining class name + $fileName = str_replace('\\', '', $classNameRelativeToProxyNamespace); + + return $proxyDir . DIRECTORY_SEPARATOR . $fileName . '.php'; + } + + /** + * Registers and returns autoloader callback for the given proxy dir and namespace. + * + * @param string $proxyDir + * @param string $proxyNamespace + * @param callable|null $notFoundCallback Invoked when the proxy file is not found. + * + * @return \Closure + * + * @throws InvalidArgumentException + */ + public static function register($proxyDir, $proxyNamespace, $notFoundCallback = null) + { + $proxyNamespace = ltrim($proxyNamespace, '\\'); + + if ( ! (null === $notFoundCallback || is_callable($notFoundCallback))) { + throw InvalidArgumentException::invalidClassNotFoundCallback($notFoundCallback); + } + + $autoloader = function ($className) use ($proxyDir, $proxyNamespace, $notFoundCallback) { + if (0 === strpos($className, $proxyNamespace)) { + $file = Autoloader::resolveFile($proxyDir, $proxyNamespace, $className); + + if ($notFoundCallback && ! file_exists($file)) { + call_user_func($notFoundCallback, $proxyDir, $proxyNamespace, $className); + } + + require $file; + } + }; + + spl_autoload_register($autoloader); + + return $autoloader; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/InvalidArgumentException.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..dff54d9f --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/InvalidArgumentException.php @@ -0,0 +1,92 @@ +. + */ + +namespace Doctrine\Common\Proxy\Exception; + +use Doctrine\Common\Persistence\Proxy; +use InvalidArgumentException as BaseInvalidArgumentException; + +/** + * Proxy Invalid Argument Exception. + * + * @link www.doctrine-project.org + * @since 2.4 + * @author Marco Pivetta + */ +class InvalidArgumentException extends BaseInvalidArgumentException implements ProxyException +{ + /** + * @return self + */ + public static function proxyDirectoryRequired() + { + return new self('You must configure a proxy directory. See docs for details'); + } + + /** + * @param string $className + * @param string $proxyNamespace + * + * @return self + */ + public static function notProxyClass($className, $proxyNamespace) + { + return new self(sprintf('The class "%s" is not part of the proxy namespace "%s"', $className, $proxyNamespace)); + } + + /** + * @param string $name + * + * @return self + */ + public static function invalidPlaceholder($name) + { + return new self(sprintf('Provided placeholder for "%s" must be either a string or a valid callable', $name)); + } + + /** + * @return self + */ + public static function proxyNamespaceRequired() + { + return new self('You must configure a proxy namespace'); + } + + /** + * @param Proxy $proxy + * + * @return self + */ + public static function unitializedProxyExpected(Proxy $proxy) + { + return new self(sprintf('Provided proxy of type "%s" must not be initialized.', get_class($proxy))); + } + + /** + * @param mixed $callback + * + * @return self + */ + public static function invalidClassNotFoundCallback($callback) + { + $type = is_object($callback) ? get_class($callback) : gettype($callback); + + return new self(sprintf('Invalid \$notFoundCallback given: must be a callable, "%s" given', $type)); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/OutOfBoundsException.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/OutOfBoundsException.php new file mode 100644 index 00000000..3c7415c6 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/OutOfBoundsException.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\Common\Proxy\Exception; + +use OutOfBoundsException as BaseOutOfBoundsException; + +/** + * Proxy Invalid Argument Exception. + * + * @link www.doctrine-project.org + * @author Fredrik Wendel + */ +class OutOfBoundsException extends BaseOutOfBoundsException implements ProxyException +{ + /** + * @param string $className + * @param string $idField + * + * @return self + */ + public static function missingPrimaryKeyValue($className, $idField) + { + return new self(sprintf("Missing value for primary key %s on %s", $idField, $className)); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/ProxyException.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/ProxyException.php new file mode 100644 index 00000000..0d1ff140 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/ProxyException.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\Common\Proxy\Exception; + +/** + * Base exception interface for proxy exceptions. + * + * @link www.doctrine-project.org + * @since 2.4 + * @author Marco Pivetta + */ +interface ProxyException +{ +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/UnexpectedValueException.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/UnexpectedValueException.php new file mode 100644 index 00000000..ec980b59 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/UnexpectedValueException.php @@ -0,0 +1,88 @@ +. + */ + +namespace Doctrine\Common\Proxy\Exception; + +use UnexpectedValueException as BaseUnexpectedValueException; + +/** + * Proxy Unexpected Value Exception. + * + * @link www.doctrine-project.org + * @since 2.4 + * @author Marco Pivetta + */ +class UnexpectedValueException extends BaseUnexpectedValueException implements ProxyException +{ + /** + * @param string $proxyDirectory + * + * @return self + */ + public static function proxyDirectoryNotWritable($proxyDirectory) + { + return new self(sprintf('Your proxy directory "%s" must be writable', $proxyDirectory)); + } + + /** + * @param string $className + * @param string $methodName + * @param string $parameterName + * @param \Exception|null $previous + * + * @return self + */ + public static function invalidParameterTypeHint( + $className, + $methodName, + $parameterName, + \Exception $previous = null + ) { + return new self( + sprintf( + 'The type hint of parameter "%s" in method "%s" in class "%s" is invalid.', + $parameterName, + $methodName, + $className + ), + 0, + $previous + ); + } + + /** + * @param $className + * @param $methodName + * @param \Exception|null $previous + * + * @return self + */ + public static function invalidReturnTypeHint($className, $methodName, \Exception $previous = null) + { + return new self( + sprintf( + 'The return type of method "%s" in class "%s" is invalid.', + $methodName, + $className + ), + 0, + $previous + ); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Proxy.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Proxy.php new file mode 100644 index 00000000..bd2b0887 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Proxy.php @@ -0,0 +1,90 @@ +. + */ + +namespace Doctrine\Common\Proxy; + +use Closure; +use Doctrine\Common\Persistence\Proxy as BaseProxy; + +/** + * Interface for proxy classes. + * + * @author Roman Borschel + * @author Marco Pivetta + * @since 2.4 + */ +interface Proxy extends BaseProxy +{ + /** + * Marks the proxy as initialized or not. + * + * @param boolean $initialized + * + * @return void + */ + public function __setInitialized($initialized); + + /** + * Sets the initializer callback to be used when initializing the proxy. That + * initializer should accept 3 parameters: $proxy, $method and $params. Those + * are respectively the proxy object that is being initialized, the method name + * that triggered initialization and the parameters passed to that method. + * + * @param Closure|null $initializer + * + * @return void + */ + public function __setInitializer(Closure $initializer = null); + + /** + * Retrieves the initializer callback used to initialize the proxy. + * + * @see __setInitializer + * + * @return Closure|null + */ + public function __getInitializer(); + + /** + * Sets the callback to be used when cloning the proxy. That initializer should accept + * a single parameter, which is the cloned proxy instance itself. + * + * @param Closure|null $cloner + * + * @return void + */ + public function __setCloner(Closure $cloner = null); + + /** + * Retrieves the callback to be used when cloning the proxy. + * + * @see __setCloner + * + * @return Closure|null + */ + public function __getCloner(); + + /** + * Retrieves the list of lazy loaded properties for a given proxy + * + * @return array Keys are the property names, and values are the default values + * for those properties. + */ + public function __getLazyProperties(); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyDefinition.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyDefinition.php new file mode 100644 index 00000000..48b149a3 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyDefinition.php @@ -0,0 +1,70 @@ +. + */ + +namespace Doctrine\Common\Proxy; + +/** + * Definition structure how to create a proxy. + * + * @author Benjamin Eberlei + */ +class ProxyDefinition +{ + /** + * @var string + */ + public $proxyClassName; + + /** + * @var array + */ + public $identifierFields; + + /** + * @var \ReflectionProperty[] + */ + public $reflectionFields; + + /** + * @var callable + */ + public $initializer; + + /** + * @var callable + */ + public $cloner; + + /** + * @param string $proxyClassName + * @param array $identifierFields + * @param array $reflectionFields + * @param callable $initializer + * @param callable $cloner + */ + public function __construct($proxyClassName, array $identifierFields, array $reflectionFields, $initializer, $cloner) + { + $this->proxyClassName = $proxyClassName; + $this->identifierFields = $identifierFields; + $this->reflectionFields = $reflectionFields; + $this->initializer = $initializer; + $this->cloner = $cloner; + } +} + diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php new file mode 100644 index 00000000..62f4167f --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php @@ -0,0 +1,1093 @@ +. + */ + +namespace Doctrine\Common\Proxy; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\Common\Proxy\Exception\InvalidArgumentException; +use Doctrine\Common\Proxy\Exception\UnexpectedValueException; +use Doctrine\Common\Util\ClassUtils; + +/** + * This factory is used to generate proxy classes. + * It builds proxies from given parameters, a template and class metadata. + * + * @author Marco Pivetta + * @since 2.4 + */ +class ProxyGenerator +{ + /** + * Used to match very simple id methods that don't need + * to be decorated since the identifier is known. + */ + const PATTERN_MATCH_ID_METHOD = '((public\s+)?(function\s+%s\s*\(\)\s*)\s*(?::\s*\??\s*\\\\?[a-z_\x7f-\xff][\w\x7f-\xff]*(?:\\\\[a-z_\x7f-\xff][\w\x7f-\xff]*)*\s*)?{\s*return\s*\$this->%s;\s*})i'; + + /** + * The namespace that contains all proxy classes. + * + * @var string + */ + private $proxyNamespace; + + /** + * The directory that contains all proxy classes. + * + * @var string + */ + private $proxyDirectory; + + /** + * Map of callables used to fill in placeholders set in the template. + * + * @var string[]|callable[] + */ + protected $placeholders = [ + 'baseProxyInterface' => Proxy::class, + 'additionalProperties' => '', + ]; + + /** + * Template used as a blueprint to generate proxies. + * + * @var string + */ + protected $proxyClassTemplate = '; + +/** + * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PROXY GENERATOR + */ +class extends \ implements \ +{ + /** + * @var \Closure the callback responsible for loading properties in the proxy object. This callback is called with + * three parameters, being respectively the proxy object to be initialized, the method that triggered the + * initialization process and an array of ordered parameters that were passed to that method. + * + * @see \Doctrine\Common\Persistence\Proxy::__setInitializer + */ + public $__initializer__; + + /** + * @var \Closure the callback responsible of loading properties that need to be copied in the cloned object + * + * @see \Doctrine\Common\Persistence\Proxy::__setCloner + */ + public $__cloner__; + + /** + * @var boolean flag indicating if this object was already initialized + * + * @see \Doctrine\Common\Persistence\Proxy::__isInitialized + */ + public $__isInitialized__ = false; + + /** + * @var array properties to be lazy loaded, with keys being the property + * names and values being their default values + * + * @see \Doctrine\Common\Persistence\Proxy::__getLazyProperties + */ + public static $lazyPropertiesDefaults = []; + + + + + + + + + + + + + + + + + + /** + * Forces initialization of the proxy + */ + public function __load() + { + $this->__initializer__ && $this->__initializer__->__invoke($this, \'__load\', []); + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __isInitialized() + { + return $this->__isInitialized__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setInitialized($initialized) + { + $this->__isInitialized__ = $initialized; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setInitializer(\Closure $initializer = null) + { + $this->__initializer__ = $initializer; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __getInitializer() + { + return $this->__initializer__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setCloner(\Closure $cloner = null) + { + $this->__cloner__ = $cloner; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific cloning logic + */ + public function __getCloner() + { + return $this->__cloner__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + * @static + */ + public function __getLazyProperties() + { + return self::$lazyPropertiesDefaults; + } + + +} +'; + + /** + * Initializes a new instance of the ProxyFactory class that is + * connected to the given EntityManager. + * + * @param string $proxyDirectory The directory to use for the proxy classes. It must exist. + * @param string $proxyNamespace The namespace to use for the proxy classes. + * + * @throws InvalidArgumentException + */ + public function __construct($proxyDirectory, $proxyNamespace) + { + if ( ! $proxyDirectory) { + throw InvalidArgumentException::proxyDirectoryRequired(); + } + + if ( ! $proxyNamespace) { + throw InvalidArgumentException::proxyNamespaceRequired(); + } + + $this->proxyDirectory = $proxyDirectory; + $this->proxyNamespace = $proxyNamespace; + } + + /** + * Sets a placeholder to be replaced in the template. + * + * @param string $name + * @param string|callable $placeholder + * + * @throws InvalidArgumentException + */ + public function setPlaceholder($name, $placeholder) + { + if ( ! is_string($placeholder) && ! is_callable($placeholder)) { + throw InvalidArgumentException::invalidPlaceholder($name); + } + + $this->placeholders[$name] = $placeholder; + } + + /** + * Sets the base template used to create proxy classes. + * + * @param string $proxyClassTemplate + */ + public function setProxyClassTemplate($proxyClassTemplate) + { + $this->proxyClassTemplate = (string) $proxyClassTemplate; + } + + /** + * Generates a proxy class file. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class Metadata for the original class. + * @param string|bool $fileName Filename (full path) for the generated class. If none is given, eval() is used. + * + * @throws UnexpectedValueException + */ + public function generateProxyClass(ClassMetadata $class, $fileName = false) + { + preg_match_all('(<([a-zA-Z]+)>)', $this->proxyClassTemplate, $placeholderMatches); + + $placeholderMatches = array_combine($placeholderMatches[0], $placeholderMatches[1]); + $placeholders = []; + + foreach ($placeholderMatches as $placeholder => $name) { + $placeholders[$placeholder] = isset($this->placeholders[$name]) + ? $this->placeholders[$name] + : [$this, 'generate' . $name]; + } + + foreach ($placeholders as & $placeholder) { + if (is_callable($placeholder)) { + $placeholder = call_user_func($placeholder, $class); + } + } + + $proxyCode = strtr($this->proxyClassTemplate, $placeholders); + + if ( ! $fileName) { + $proxyClassName = $this->generateNamespace($class) . '\\' . $this->generateProxyShortClassName($class); + + if ( ! class_exists($proxyClassName)) { + eval(substr($proxyCode, 5)); + } + + return; + } + + $parentDirectory = dirname($fileName); + + if ( ! is_dir($parentDirectory) && (false === @mkdir($parentDirectory, 0775, true))) { + throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory); + } + + if ( ! is_writable($parentDirectory)) { + throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory); + } + + $tmpFileName = $fileName . '.' . uniqid('', true); + + file_put_contents($tmpFileName, $proxyCode); + @chmod($tmpFileName, 0664); + rename($tmpFileName, $fileName); + } + + /** + * Generates the proxy short class name to be used in the template. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateProxyShortClassName(ClassMetadata $class) + { + $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace); + $parts = explode('\\', strrev($proxyClassName), 2); + + return strrev($parts[0]); + } + + /** + * Generates the proxy namespace. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateNamespace(ClassMetadata $class) + { + $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace); + $parts = explode('\\', strrev($proxyClassName), 2); + + return strrev($parts[1]); + } + + /** + * Generates the original class name. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateClassName(ClassMetadata $class) + { + return ltrim($class->getName(), '\\'); + } + + /** + * Generates the array representation of lazy loaded public properties and their default values. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateLazyPropertiesDefaults(ClassMetadata $class) + { + $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class); + $values = []; + + foreach ($lazyPublicProperties as $key => $value) { + $values[] = var_export($key, true) . ' => ' . var_export($value, true); + } + + return implode(', ', $values); + } + + /** + * Generates the constructor code (un-setting public lazy loaded properties, setting identifier field values). + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateConstructorImpl(ClassMetadata $class) + { + $constructorImpl = <<<'EOT' + /** + * @param \Closure $initializer + * @param \Closure $cloner + */ + public function __construct($initializer = null, $cloner = null) + { + +EOT; + $toUnset = []; + + foreach ($this->getLazyLoadedPublicProperties($class) as $lazyPublicProperty => $unused) { + $toUnset[] = '$this->' . $lazyPublicProperty; + } + + $constructorImpl .= (empty($toUnset) ? '' : ' unset(' . implode(', ', $toUnset) . ");\n") + . <<<'EOT' + + $this->__initializer__ = $initializer; + $this->__cloner__ = $cloner; + } +EOT; + + return $constructorImpl; + } + + /** + * Generates the magic getter invoked when lazy loaded public properties are requested. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateMagicGet(ClassMetadata $class) + { + $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class)); + $reflectionClass = $class->getReflectionClass(); + $hasParentGet = false; + $returnReference = ''; + $inheritDoc = ''; + + if ($reflectionClass->hasMethod('__get')) { + $hasParentGet = true; + $inheritDoc = '{@inheritDoc}'; + + if ($reflectionClass->getMethod('__get')->returnsReference()) { + $returnReference = '& '; + } + } + + if (empty($lazyPublicProperties) && ! $hasParentGet) { + return ''; + } + + $magicGet = <<__getLazyProperties())) { + $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]); + + return $this->$name; + } + + +EOT; + } + + if ($hasParentGet) { + $magicGet .= <<<'EOT' + $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]); + + return parent::__get($name); + +EOT; + } else { + $magicGet .= <<<'EOT' + trigger_error(sprintf('Undefined property: %s::$%s', __CLASS__, $name), E_USER_NOTICE); + +EOT; + } + + $magicGet .= " }"; + + return $magicGet; + } + + /** + * Generates the magic setter (currently unused). + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateMagicSet(ClassMetadata $class) + { + $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class); + $hasParentSet = $class->getReflectionClass()->hasMethod('__set'); + + if (empty($lazyPublicProperties) && ! $hasParentSet) { + return ''; + } + + $inheritDoc = $hasParentSet ? '{@inheritDoc}' : ''; + $magicSet = <<__getLazyProperties())) { + $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]); + + $this->$name = $value; + + return; + } + + +EOT; + } + + if ($hasParentSet) { + $magicSet .= <<<'EOT' + $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]); + + return parent::__set($name, $value); +EOT; + } else { + $magicSet .= " \$this->\$name = \$value;"; + } + + $magicSet .= "\n }"; + + return $magicSet; + } + + /** + * Generates the magic issetter invoked when lazy loaded public properties are checked against isset(). + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateMagicIsset(ClassMetadata $class) + { + $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class)); + $hasParentIsset = $class->getReflectionClass()->hasMethod('__isset'); + + if (empty($lazyPublicProperties) && ! $hasParentIsset) { + return ''; + } + + $inheritDoc = $hasParentIsset ? '{@inheritDoc}' : ''; + $magicIsset = <<__getLazyProperties())) { + $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]); + + return isset($this->$name); + } + + +EOT; + } + + if ($hasParentIsset) { + $magicIsset .= <<<'EOT' + $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]); + + return parent::__isset($name); + +EOT; + } else { + $magicIsset .= " return false;"; + } + + return $magicIsset . "\n }"; + } + + /** + * Generates implementation for the `__sleep` method of proxies. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateSleepImpl(ClassMetadata $class) + { + $hasParentSleep = $class->getReflectionClass()->hasMethod('__sleep'); + $inheritDoc = $hasParentSleep ? '{@inheritDoc}' : ''; + $sleepImpl = <<__isInitialized__) { + $properties = array_diff($properties, array_keys($this->__getLazyProperties())); + } + + return $properties; + } +EOT; + } + + $allProperties = ['__isInitialized__']; + + /* @var $prop \ReflectionProperty */ + foreach ($class->getReflectionClass()->getProperties() as $prop) { + if ($prop->isStatic()) { + continue; + } + + $allProperties[] = $prop->isPrivate() + ? "\0" . $prop->getDeclaringClass()->getName() . "\0" . $prop->getName() + : $prop->getName(); + } + + $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class)); + $protectedProperties = array_diff($allProperties, $lazyPublicProperties); + + foreach ($allProperties as &$property) { + $property = var_export($property, true); + } + + foreach ($protectedProperties as &$property) { + $property = var_export($property, true); + } + + $allProperties = implode(', ', $allProperties); + $protectedProperties = implode(', ', $protectedProperties); + + return $sleepImpl . <<__isInitialized__) { + return [$allProperties]; + } + + return [$protectedProperties]; + } +EOT; + } + + /** + * Generates implementation for the `__wakeup` method of proxies. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateWakeupImpl(ClassMetadata $class) + { + $unsetPublicProperties = []; + $hasWakeup = $class->getReflectionClass()->hasMethod('__wakeup'); + + foreach (array_keys($this->getLazyLoadedPublicProperties($class)) as $lazyPublicProperty) { + $unsetPublicProperties[] = '$this->' . $lazyPublicProperty; + } + + $shortName = $this->generateProxyShortClassName($class); + $inheritDoc = $hasWakeup ? '{@inheritDoc}' : ''; + $wakeupImpl = <<__isInitialized__) { + \$this->__initializer__ = function ($shortName \$proxy) { + \$proxy->__setInitializer(null); + \$proxy->__setCloner(null); + + \$existingProperties = get_object_vars(\$proxy); + + foreach (\$proxy->__getLazyProperties() as \$property => \$defaultValue) { + if ( ! array_key_exists(\$property, \$existingProperties)) { + \$proxy->\$property = \$defaultValue; + } + } + }; + +EOT; + + if ( ! empty($unsetPublicProperties)) { + $wakeupImpl .= "\n unset(" . implode(', ', $unsetPublicProperties) . ");"; + } + + $wakeupImpl .= "\n }"; + + if ($hasWakeup) { + $wakeupImpl .= "\n parent::__wakeup();"; + } + + $wakeupImpl .= "\n }"; + + return $wakeupImpl; + } + + /** + * Generates implementation for the `__clone` method of proxies. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateCloneImpl(ClassMetadata $class) + { + $hasParentClone = $class->getReflectionClass()->hasMethod('__clone'); + $inheritDoc = $hasParentClone ? '{@inheritDoc}' : ''; + $callParentClone = $hasParentClone ? "\n parent::__clone();\n" : ''; + + return <<__cloner__ && \$this->__cloner__->__invoke(\$this, '__clone', []); +$callParentClone } +EOT; + } + + /** + * Generates decorated methods by picking those available in the parent class. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateMethods(ClassMetadata $class) + { + $methods = ''; + $methodNames = []; + $reflectionMethods = $class->getReflectionClass()->getMethods(\ReflectionMethod::IS_PUBLIC); + $skippedMethods = [ + '__sleep' => true, + '__clone' => true, + '__wakeup' => true, + '__get' => true, + '__set' => true, + '__isset' => true, + ]; + + foreach ($reflectionMethods as $method) { + $name = $method->getName(); + + if ( + $method->isConstructor() || + isset($skippedMethods[strtolower($name)]) || + isset($methodNames[$name]) || + $method->isFinal() || + $method->isStatic() || + ( ! $method->isPublic()) + ) { + continue; + } + + $methodNames[$name] = true; + $methods .= "\n /**\n" + . " * {@inheritDoc}\n" + . " */\n" + . ' public function '; + + if ($method->returnsReference()) { + $methods .= '&'; + } + + $methods .= $name . '(' . $this->buildParametersString($class, $method, $method->getParameters()) . ')'; + $methods .= $this->getMethodReturnType($method); + $methods .= "\n" . ' {' . "\n"; + + if ($this->isShortIdentifierGetter($method, $class)) { + $identifier = lcfirst(substr($name, 3)); + $fieldType = $class->getTypeOfField($identifier); + $cast = in_array($fieldType, ['integer', 'smallint']) ? '(int) ' : ''; + + $methods .= ' if ($this->__isInitialized__ === false) {' . "\n"; + $methods .= ' '; + $methods .= $this->shouldProxiedMethodReturn($method) ? 'return ' : ''; + $methods .= $cast . ' parent::' . $method->getName() . "();\n"; + $methods .= ' }' . "\n\n"; + } + + $invokeParamsString = implode(', ', $this->getParameterNamesForInvoke($method->getParameters())); + $callParamsString = implode(', ', $this->getParameterNamesForParentCall($method->getParameters())); + + $methods .= "\n \$this->__initializer__ " + . "&& \$this->__initializer__->__invoke(\$this, " . var_export($name, true) + . ", [" . $invokeParamsString . "]);" + . "\n\n " + . ($this->shouldProxiedMethodReturn($method) ? 'return ' : '') + . "parent::" . $name . '(' . $callParamsString . ');' + . "\n" . ' }' . "\n"; + } + + return $methods; + } + + /** + * Generates the Proxy file name. + * + * @param string $className + * @param string $baseDirectory Optional base directory for proxy file name generation. + * If not specified, the directory configured on the Configuration of the + * EntityManager will be used by this factory. + * + * @return string + */ + public function getProxyFileName($className, $baseDirectory = null) + { + $baseDirectory = $baseDirectory ?: $this->proxyDirectory; + + return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Proxy::MARKER + . str_replace('\\', '', $className) . '.php'; + } + + /** + * Checks if the method is a short identifier getter. + * + * What does this mean? For proxy objects the identifier is already known, + * however accessing the getter for this identifier usually triggers the + * lazy loading, leading to a query that may not be necessary if only the + * ID is interesting for the userland code (for example in views that + * generate links to the entity, but do not display anything else). + * + * @param \ReflectionMethod $method + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return boolean + */ + private function isShortIdentifierGetter($method, ClassMetadata $class) + { + $identifier = lcfirst(substr($method->getName(), 3)); + $startLine = $method->getStartLine(); + $endLine = $method->getEndLine(); + $cheapCheck = ( + $method->getNumberOfParameters() == 0 + && substr($method->getName(), 0, 3) == 'get' + && in_array($identifier, $class->getIdentifier(), true) + && $class->hasField($identifier) + && (($endLine - $startLine) <= 4) + ); + + if ($cheapCheck) { + $code = file($method->getDeclaringClass()->getFileName()); + $code = trim(implode(' ', array_slice($code, $startLine - 1, $endLine - $startLine + 1))); + + $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier); + + if (preg_match($pattern, $code)) { + return true; + } + } + + return false; + } + + /** + * Generates the list of public properties to be lazy loaded, with their default values. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return mixed[] + */ + private function getLazyLoadedPublicProperties(ClassMetadata $class) + { + $defaultProperties = $class->getReflectionClass()->getDefaultProperties(); + $properties = []; + + foreach ($class->getReflectionClass()->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + $name = $property->getName(); + + if (($class->hasField($name) || $class->hasAssociation($name)) && ! $class->isIdentifier($name)) { + $properties[$name] = $defaultProperties[$name]; + } + } + + return $properties; + } + + /** + * @param ClassMetadata $class + * @param \ReflectionMethod $method + * @param \ReflectionParameter[] $parameters + * + * @return string + */ + private function buildParametersString(ClassMetadata $class, \ReflectionMethod $method, array $parameters) + { + $parameterDefinitions = []; + + /* @var $param \ReflectionParameter */ + foreach ($parameters as $param) { + $parameterDefinition = ''; + + if ($parameterType = $this->getParameterType($class, $method, $param)) { + $parameterDefinition .= $parameterType . ' '; + } + + if ($param->isPassedByReference()) { + $parameterDefinition .= '&'; + } + + if (method_exists($param, 'isVariadic') && $param->isVariadic()) { + $parameterDefinition .= '...'; + } + + $parameters[] = '$' . $param->getName(); + $parameterDefinition .= '$' . $param->getName(); + + if ($param->isDefaultValueAvailable()) { + $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true); + } + + $parameterDefinitions[] = $parameterDefinition; + } + + return implode(', ', $parameterDefinitions); + } + + /** + * @param ClassMetadata $class + * @param \ReflectionMethod $method + * @param \ReflectionParameter $parameter + * + * @return string|null + */ + private function getParameterType(ClassMetadata $class, \ReflectionMethod $method, \ReflectionParameter $parameter) + { + if (method_exists($parameter, 'hasType')) { + if ( ! $parameter->hasType()) { + return ''; + } + + return $this->formatType($parameter->getType(), $parameter->getDeclaringFunction(), $parameter); + } + + // For PHP 5.x, we need to pick the type hint in the old way (to be removed for PHP 7.0+) + if ($parameter->isArray()) { + return 'array'; + } + + if ($parameter->isCallable()) { + return 'callable'; + } + + try { + $parameterClass = $parameter->getClass(); + + if ($parameterClass) { + return '\\' . $parameterClass->getName(); + } + } catch (\ReflectionException $previous) { + throw UnexpectedValueException::invalidParameterTypeHint( + $class->getName(), + $method->getName(), + $parameter->getName(), + $previous + ); + } + + return null; + } + + /** + * @param \ReflectionParameter[] $parameters + * + * @return string[] + */ + private function getParameterNamesForInvoke(array $parameters) + { + return array_map( + function (\ReflectionParameter $parameter) { + return '$' . $parameter->getName(); + }, + $parameters + ); + } + + /** + * @param \ReflectionParameter[] $parameters + * + * @return string[] + */ + private function getParameterNamesForParentCall(array $parameters) + { + return array_map( + function (\ReflectionParameter $parameter) { + $name = ''; + + if (method_exists($parameter, 'isVariadic') && $parameter->isVariadic()) { + $name .= '...'; + } + + $name .= '$' . $parameter->getName(); + + return $name; + }, + $parameters + ); + } + + /** + * @Param \ReflectionMethod $method + * + * @return string + */ + private function getMethodReturnType(\ReflectionMethod $method) + { + if ( ! method_exists($method, 'hasReturnType') || ! $method->hasReturnType()) { + return ''; + } + + return ': ' . $this->formatType($method->getReturnType(), $method); + } + + /** + * @param \ReflectionMethod $method + * + * @return bool + */ + private function shouldProxiedMethodReturn(\ReflectionMethod $method) + { + if ( ! method_exists($method, 'hasReturnType') || ! $method->hasReturnType()) { + return true; + } + + return 'void' !== strtolower($this->formatType($method->getReturnType(), $method)); + } + + /** + * @param \ReflectionType $type + * @param \ReflectionMethod $method + * @param \ReflectionParameter|null $parameter + * + * @return string + */ + private function formatType( + \ReflectionType $type, + \ReflectionMethod $method, + \ReflectionParameter $parameter = null + ) { + $name = method_exists($type, 'getName') ? $type->getName() : (string) $type; + $nameLower = strtolower($name); + + if ('self' === $nameLower) { + $name = $method->getDeclaringClass()->getName(); + } + + if ('parent' === $nameLower) { + $name = $method->getDeclaringClass()->getParentClass()->getName(); + } + + if ( ! $type->isBuiltin() && ! class_exists($name) && ! interface_exists($name)) { + if (null !== $parameter) { + throw UnexpectedValueException::invalidParameterTypeHint( + $method->getDeclaringClass()->getName(), + $method->getName(), + $parameter->getName() + ); + } + + throw UnexpectedValueException::invalidReturnTypeHint( + $method->getDeclaringClass()->getName(), + $method->getName() + ); + } + + if ( ! $type->isBuiltin()) { + $name = '\\' . $name; + } + + if ($type->allowsNull() + && (null === $parameter || ! $parameter->isDefaultValueAvailable() || null !== $parameter->getDefaultValue()) + ) { + $name = '?' . $name; + } + + return $name; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/ClassFinderInterface.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/ClassFinderInterface.php new file mode 100644 index 00000000..639fd69a --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/ClassFinderInterface.php @@ -0,0 +1,37 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +/** + * Finds a class in a PSR-0 structure. + * + * @author Karoly Negyesi + */ +interface ClassFinderInterface +{ + /** + * Finds a class. + * + * @param string $class The name of the class. + * + * @return string|null The name of the class or NULL if not found. + */ + public function findFile($class); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/Psr0FindFile.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/Psr0FindFile.php new file mode 100644 index 00000000..418bb0f7 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/Psr0FindFile.php @@ -0,0 +1,79 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +/** + * Finds a class in a PSR-0 structure. + * + * @author Karoly Negyesi + */ +class Psr0FindFile implements ClassFinderInterface +{ + /** + * The PSR-0 prefixes. + * + * @var array + */ + protected $prefixes; + + /** + * @param array $prefixes An array of prefixes. Each key is a PHP namespace and each value is + * a list of directories. + */ + public function __construct($prefixes) + { + $this->prefixes = $prefixes; + } + + /** + * {@inheritDoc} + */ + public function findFile($class) + { + $lastNsPos = strrpos($class, '\\'); + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + if (false !== $lastNsPos) { + // namespaced class name + $classPath = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, 0, $lastNsPos)) . DIRECTORY_SEPARATOR; + $className = substr($class, $lastNsPos + 1); + } else { + // PEAR-like class name + $classPath = null; + $className = $class; + } + + $classPath .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; + + foreach ($this->prefixes as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (is_file($dir . DIRECTORY_SEPARATOR . $classPath)) { + return $dir . DIRECTORY_SEPARATOR . $classPath; + } + } + } + } + + return null; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/ReflectionProviderInterface.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/ReflectionProviderInterface.php new file mode 100644 index 00000000..3d970eee --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/ReflectionProviderInterface.php @@ -0,0 +1,48 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +interface ReflectionProviderInterface +{ + /** + * Gets the ReflectionClass equivalent for this class. + * + * @return \ReflectionClass + */ + public function getReflectionClass(); + + /** + * Gets the ReflectionMethod equivalent for this class. + * + * @param string $name + * + * @return \ReflectionMethod + */ + public function getReflectionMethod($name); + + /** + * Gets the ReflectionProperty equivalent for this class. + * + * @param string $name + * + * @return \ReflectionProperty + */ + public function getReflectionProperty($name); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/RuntimePublicReflectionProperty.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/RuntimePublicReflectionProperty.php new file mode 100644 index 00000000..4e8ef530 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/RuntimePublicReflectionProperty.php @@ -0,0 +1,76 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +use Doctrine\Common\Proxy\Proxy; +use ReflectionProperty; + +/** + * PHP Runtime Reflection Public Property - special overrides for public properties. + * + * @author Marco Pivetta + * @since 2.4 + */ +class RuntimePublicReflectionProperty extends ReflectionProperty +{ + /** + * {@inheritDoc} + * + * Checks is the value actually exist before fetching it. + * This is to avoid calling `__get` on the provided $object if it + * is a {@see \Doctrine\Common\Proxy\Proxy}. + */ + public function getValue($object = null) + { + $name = $this->getName(); + + if ($object instanceof Proxy && ! $object->__isInitialized()) { + $originalInitializer = $object->__getInitializer(); + $object->__setInitializer(null); + $val = isset($object->$name) ? $object->$name : null; + $object->__setInitializer($originalInitializer); + + return $val; + } + + return isset($object->$name) ? parent::getValue($object) : null; + } + + /** + * {@inheritDoc} + * + * Avoids triggering lazy loading via `__set` if the provided object + * is a {@see \Doctrine\Common\Proxy\Proxy}. + * @link https://bugs.php.net/bug.php?id=63463 + */ + public function setValue($object, $value = null) + { + if ( ! ($object instanceof Proxy && ! $object->__isInitialized())) { + parent::setValue($object, $value); + + return; + } + + $originalInitializer = $object->__getInitializer(); + $object->__setInitializer(null); + parent::setValue($object, $value); + $object->__setInitializer($originalInitializer); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionClass.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionClass.php new file mode 100644 index 00000000..2d0f5b00 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionClass.php @@ -0,0 +1,433 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +use ReflectionClass; +use ReflectionException; + +class StaticReflectionClass extends ReflectionClass +{ + /** + * The static reflection parser object. + * + * @var StaticReflectionParser + */ + private $staticReflectionParser; + + /** + * @param StaticReflectionParser $staticReflectionParser + */ + public function __construct(StaticReflectionParser $staticReflectionParser) + { + $this->staticReflectionParser = $staticReflectionParser; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return $this->staticReflectionParser->getClassName(); + } + + /** + * {@inheritDoc} + */ + public function getDocComment() + { + return $this->staticReflectionParser->getDocComment(); + } + + /** + * {@inheritDoc} + */ + public function getNamespaceName() + { + return $this->staticReflectionParser->getNamespaceName(); + } + + /** + * @return array + */ + public function getUseStatements() + { + return $this->staticReflectionParser->getUseStatements(); + } + + /** + * {@inheritDoc} + */ + public function getMethod($name) + { + return $this->staticReflectionParser->getReflectionMethod($name); + } + + /** + * {@inheritDoc} + */ + public function getProperty($name) + { + return $this->staticReflectionParser->getReflectionProperty($name); + } + + /** + * {@inheritDoc} + */ + public static function export($argument, $return = false) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getConstant($name) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getConstants() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getConstructor() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getDefaultProperties() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getEndLine() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getExtension() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getExtensionName() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getFileName() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getInterfaceNames() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getInterfaces() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getMethods($filter = null) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getModifiers() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getParentClass() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getProperties($filter = null) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getShortName() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getStartLine() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getStaticProperties() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getStaticPropertyValue($name, $default = '') + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getTraitAliases() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getTraitNames() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getTraits() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function hasConstant($name) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function hasMethod($name) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function hasProperty($name) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function implementsInterface($interface) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function inNamespace() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isAbstract() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isCloneable() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isFinal() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isInstance($object) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isInstantiable() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isInterface() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isInternal() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isIterateable() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isSubclassOf($class) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isTrait() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isUserDefined() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function newInstance($args) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function newInstanceArgs(array $args = []) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function newInstanceWithoutConstructor() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function setStaticPropertyValue($name, $value) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + throw new ReflectionException('Method not implemented'); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionMethod.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionMethod.php new file mode 100644 index 00000000..8d5380b3 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionMethod.php @@ -0,0 +1,362 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +use ReflectionException; +use ReflectionMethod; + +class StaticReflectionMethod extends ReflectionMethod +{ + /** + * The PSR-0 parser object. + * + * @var StaticReflectionParser + */ + protected $staticReflectionParser; + + /** + * The name of the method. + * + * @var string + */ + protected $methodName; + + /** + * @param StaticReflectionParser $staticReflectionParser + * @param string $methodName + */ + public function __construct(StaticReflectionParser $staticReflectionParser, $methodName) + { + $this->staticReflectionParser = $staticReflectionParser; + $this->methodName = $methodName; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return $this->methodName; + } + + /** + * @return StaticReflectionParser + */ + protected function getStaticReflectionParser() + { + return $this->staticReflectionParser->getStaticReflectionParserForDeclaringClass('method', $this->methodName); + } + + /** + * {@inheritDoc} + */ + public function getDeclaringClass() + { + return $this->getStaticReflectionParser()->getReflectionClass(); + } + + /** + * {@inheritDoc} + */ + public function getNamespaceName() + { + return $this->getStaticReflectionParser()->getNamespaceName(); + } + + /** + * {@inheritDoc} + */ + public function getDocComment() + { + return $this->getStaticReflectionParser()->getDocComment('method', $this->methodName); + } + + /** + * @return array + */ + public function getUseStatements() + { + return $this->getStaticReflectionParser()->getUseStatements(); + } + + /** + * {@inheritDoc} + */ + public static function export($class, $name, $return = false) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getClosure($object) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getModifiers() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getPrototype() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function invoke($object, $parameter = null) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function invokeArgs($object, array $args) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isAbstract() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isConstructor() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isDestructor() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isFinal() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isPrivate() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isProtected() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isPublic() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isStatic() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function setAccessible($accessible) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getClosureThis() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getEndLine() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getExtension() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getExtensionName() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getFileName() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getNumberOfParameters() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getNumberOfRequiredParameters() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getParameters() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getShortName() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getStartLine() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getStaticVariables() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function inNamespace() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isClosure() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isDeprecated() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isInternal() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isUserDefined() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function returnsReference() + { + throw new ReflectionException('Method not implemented'); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionParser.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionParser.php new file mode 100644 index 00000000..c48e9ba7 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionParser.php @@ -0,0 +1,307 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +use Doctrine\Common\Annotations\TokenParser; +use ReflectionException; + +/** + * Parses a file for namespaces/use/class declarations. + * + * @author Karoly Negyesi + */ +class StaticReflectionParser implements ReflectionProviderInterface +{ + /** + * The fully qualified class name. + * + * @var string + */ + protected $className; + + /** + * The short class name. + * + * @var string + */ + protected $shortClassName; + + /** + * Whether the caller only wants class annotations. + * + * @var boolean. + */ + protected $classAnnotationOptimize; + + /** + * Whether the parser has run. + * + * @var boolean + */ + protected $parsed = false; + + /** + * The namespace of the class. + * + * @var string + */ + protected $namespace = ''; + + /** + * The use statements of the class. + * + * @var array + */ + protected $useStatements = []; + + /** + * The docComment of the class. + * + * @var string + */ + protected $docComment = [ + 'class' => '', + 'property' => [], + 'method' => [] + ]; + + /** + * The name of the class this class extends, if any. + * + * @var string + */ + protected $parentClassName = ''; + + /** + * The parent PSR-0 Parser. + * + * @var \Doctrine\Common\Reflection\StaticReflectionParser + */ + protected $parentStaticReflectionParser; + + /** + * Parses a class residing in a PSR-0 hierarchy. + * + * @param string $className The full, namespaced class name. + * @param ClassFinderInterface $finder A ClassFinder object which finds the class. + * @param boolean $classAnnotationOptimize Only retrieve the class docComment. + * Presumes there is only one statement per line. + */ + public function __construct($className, $finder, $classAnnotationOptimize = false) + { + $this->className = ltrim($className, '\\'); + $lastNsPos = strrpos($this->className, '\\'); + + if ($lastNsPos !== false) { + $this->namespace = substr($this->className, 0, $lastNsPos); + $this->shortClassName = substr($this->className, $lastNsPos + 1); + } else { + $this->shortClassName = $this->className; + } + + $this->finder = $finder; + $this->classAnnotationOptimize = $classAnnotationOptimize; + } + + /** + * @return void + */ + protected function parse() + { + if ($this->parsed || !$fileName = $this->finder->findFile($this->className)) { + return; + } + $this->parsed = true; + $contents = file_get_contents($fileName); + if ($this->classAnnotationOptimize) { + if (preg_match("/\A.*^\s*((abstract|final)\s+)?class\s+{$this->shortClassName}\s+/sm", $contents, $matches)) { + $contents = $matches[0]; + } + } + $tokenParser = new TokenParser($contents); + $docComment = ''; + while ($token = $tokenParser->next(false)) { + if (is_array($token)) { + switch ($token[0]) { + case T_USE: + $this->useStatements = array_merge($this->useStatements, $tokenParser->parseUseStatement()); + break; + case T_DOC_COMMENT: + $docComment = $token[1]; + break; + case T_CLASS: + $this->docComment['class'] = $docComment; + $docComment = ''; + break; + case T_VAR: + case T_PRIVATE: + case T_PROTECTED: + case T_PUBLIC: + $token = $tokenParser->next(); + if ($token[0] === T_VARIABLE) { + $propertyName = substr($token[1], 1); + $this->docComment['property'][$propertyName] = $docComment; + continue 2; + } + if ($token[0] !== T_FUNCTION) { + // For example, it can be T_FINAL. + continue 2; + } + // No break. + case T_FUNCTION: + // The next string after function is the name, but + // there can be & before the function name so find the + // string. + while (($token = $tokenParser->next()) && $token[0] !== T_STRING); + $methodName = $token[1]; + $this->docComment['method'][$methodName] = $docComment; + $docComment = ''; + break; + case T_EXTENDS: + $this->parentClassName = $tokenParser->parseClass(); + $nsPos = strpos($this->parentClassName, '\\'); + $fullySpecified = false; + if ($nsPos === 0) { + $fullySpecified = true; + } else { + if ($nsPos) { + $prefix = strtolower(substr($this->parentClassName, 0, $nsPos)); + $postfix = substr($this->parentClassName, $nsPos); + } else { + $prefix = strtolower($this->parentClassName); + $postfix = ''; + } + foreach ($this->useStatements as $alias => $use) { + if ($alias == $prefix) { + $this->parentClassName = '\\' . $use . $postfix; + $fullySpecified = true; + } + } + } + if (!$fullySpecified) { + $this->parentClassName = '\\' . $this->namespace . '\\' . $this->parentClassName; + } + break; + } + } + } + } + + /** + * @return StaticReflectionParser + */ + protected function getParentStaticReflectionParser() + { + if (empty($this->parentStaticReflectionParser)) { + $this->parentStaticReflectionParser = new static($this->parentClassName, $this->finder); + } + + return $this->parentStaticReflectionParser; + } + + /** + * @return string + */ + public function getClassName() + { + return $this->className; + } + + /** + * @return string + */ + public function getNamespaceName() + { + return $this->namespace; + } + + /** + * {@inheritDoc} + */ + public function getReflectionClass() + { + return new StaticReflectionClass($this); + } + + /** + * {@inheritDoc} + */ + public function getReflectionMethod($methodName) + { + return new StaticReflectionMethod($this, $methodName); + } + + /** + * {@inheritDoc} + */ + public function getReflectionProperty($propertyName) + { + return new StaticReflectionProperty($this, $propertyName); + } + + /** + * Gets the use statements from this file. + * + * @return array + */ + public function getUseStatements() + { + $this->parse(); + + return $this->useStatements; + } + + /** + * Gets the doc comment. + * + * @param string $type The type: 'class', 'property' or 'method'. + * @param string $name The name of the property or method, not needed for 'class'. + * + * @return string The doc comment, empty string if none. + */ + public function getDocComment($type = 'class', $name = '') + { + $this->parse(); + + return $name ? $this->docComment[$type][$name] : $this->docComment[$type]; + } + + /** + * Gets the PSR-0 parser for the declaring class. + * + * @param string $type The type: 'property' or 'method'. + * @param string $name The name of the property or method. + * + * @return StaticReflectionParser A static reflection parser for the declaring class. + * + * @throws ReflectionException + */ + public function getStaticReflectionParserForDeclaringClass($type, $name) + { + $this->parse(); + if (isset($this->docComment[$type][$name])) { + return $this; + } + if (!empty($this->parentClassName)) { + return $this->getParentStaticReflectionParser()->getStaticReflectionParserForDeclaringClass($type, $name); + } + throw new ReflectionException('Invalid ' . $type . ' "' . $name . '"'); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionProperty.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionProperty.php new file mode 100644 index 00000000..7f6522bb --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionProperty.php @@ -0,0 +1,178 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +use ReflectionException; +use ReflectionProperty; + +class StaticReflectionProperty extends ReflectionProperty +{ + /** + * The PSR-0 parser object. + * + * @var StaticReflectionParser + */ + protected $staticReflectionParser; + + /** + * The name of the property. + * + * @var string|null + */ + protected $propertyName; + + /** + * @param StaticReflectionParser $staticReflectionParser + * @param string|null $propertyName + */ + public function __construct(StaticReflectionParser $staticReflectionParser, $propertyName) + { + $this->staticReflectionParser = $staticReflectionParser; + $this->propertyName = $propertyName; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return $this->propertyName; + } + + /** + * @return StaticReflectionParser + */ + protected function getStaticReflectionParser() + { + return $this->staticReflectionParser->getStaticReflectionParserForDeclaringClass('property', $this->propertyName); + } + + /** + * {@inheritDoc} + */ + public function getDeclaringClass() + { + return $this->getStaticReflectionParser()->getReflectionClass(); + } + + /** + * {@inheritDoc} + */ + public function getDocComment() + { + return $this->getStaticReflectionParser()->getDocComment('property', $this->propertyName); + } + + /** + * @return array + */ + public function getUseStatements() + { + return $this->getStaticReflectionParser()->getUseStatements(); + } + + /** + * {@inheritDoc} + */ + public static function export($class, $name, $return = false) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getModifiers() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getValue($object = null) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isDefault() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isPrivate() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isProtected() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isPublic() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isStatic() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function setAccessible($accessible) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function setValue($object, $value = null) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + throw new ReflectionException('Method not implemented'); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Util/ClassUtils.php b/vendor/doctrine/common/lib/Doctrine/Common/Util/ClassUtils.php new file mode 100644 index 00000000..49dc7bb1 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Util/ClassUtils.php @@ -0,0 +1,109 @@ +. + */ + +namespace Doctrine\Common\Util; + +use Doctrine\Common\Persistence\Proxy; + +/** + * Class and reflection related functionality for objects that + * might or not be proxy objects at the moment. + * + * @author Benjamin Eberlei + * @author Johannes Schmitt + */ +class ClassUtils +{ + /** + * Gets the real class name of a class name that could be a proxy. + * + * @param string $class + * + * @return string + */ + public static function getRealClass($class) + { + if (false === $pos = strrpos($class, '\\'.Proxy::MARKER.'\\')) { + return $class; + } + + return substr($class, $pos + Proxy::MARKER_LENGTH + 2); + } + + /** + * Gets the real class name of an object (even if its a proxy). + * + * @param object $object + * + * @return string + */ + public static function getClass($object) + { + return self::getRealClass(get_class($object)); + } + + /** + * Gets the real parent class name of a class or object. + * + * @param string $className + * + * @return string + */ + public static function getParentClass($className) + { + return get_parent_class( self::getRealClass( $className ) ); + } + + /** + * Creates a new reflection class. + * + * @param string $class + * + * @return \ReflectionClass + */ + public static function newReflectionClass($class) + { + return new \ReflectionClass( self::getRealClass( $class ) ); + } + + /** + * Creates a new reflection object. + * + * @param object $object + * + * @return \ReflectionObject + */ + public static function newReflectionObject($object) + { + return self::newReflectionClass( self::getClass( $object ) ); + } + + /** + * Given a class name and a proxy namespace returns the proxy name. + * + * @param string $className + * @param string $proxyNamespace + * + * @return string + */ + public static function generateProxyClassName($className, $proxyNamespace) + { + return rtrim($proxyNamespace, '\\') . '\\'.Proxy::MARKER.'\\' . ltrim($className, '\\'); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Util/Debug.php b/vendor/doctrine/common/lib/Doctrine/Common/Util/Debug.php new file mode 100644 index 00000000..ae115edd --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Util/Debug.php @@ -0,0 +1,197 @@ +. + */ + +namespace Doctrine\Common\Util; + +use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Persistence\Proxy; + +/** + * Static class containing most used debug methods. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Giorgio Sironi + */ +final class Debug +{ + /** + * Private constructor (prevents instantiation). + */ + private function __construct() + { + } + + /** + * Prints a dump of the public, protected and private properties of $var. + * + * @link http://xdebug.org/ + * + * @param mixed $var The variable to dump. + * @param integer $maxDepth The maximum nesting level for object properties. + * @param boolean $stripTags Whether output should strip HTML tags. + * @param boolean $echo Send the dumped value to the output buffer + * + * @return string + */ + public static function dump($var, $maxDepth = 2, $stripTags = true, $echo = true) + { + $html = ini_get('html_errors'); + + if ($html !== true) { + ini_set('html_errors', true); + } + + if (extension_loaded('xdebug')) { + ini_set('xdebug.var_display_max_depth', $maxDepth); + } + + $var = self::export($var, $maxDepth); + + ob_start(); + var_dump($var); + + $dump = ob_get_contents(); + + ob_end_clean(); + + $dumpText = ($stripTags ? strip_tags(html_entity_decode($dump)) : $dump); + + ini_set('html_errors', $html); + + if ($echo) { + echo $dumpText; + } + + return $dumpText; + } + + /** + * @param mixed $var + * @param int $maxDepth + * + * @return mixed + */ + public static function export($var, $maxDepth) + { + $return = null; + $isObj = is_object($var); + + if ($var instanceof Collection) { + $var = $var->toArray(); + } + + if (! $maxDepth) { + return is_object($var) ? get_class($var) + : (is_array($var) ? 'Array(' . count($var) . ')' : $var); + } + + if (is_array($var)) { + $return = []; + + foreach ($var as $k => $v) { + $return[$k] = self::export($v, $maxDepth - 1); + } + + return $return; + } + + if (! $isObj) { + return $var; + } + + $return = new \stdclass(); + if ($var instanceof \DateTimeInterface) { + $return->__CLASS__ = get_class($var); + $return->date = $var->format('c'); + $return->timezone = $var->getTimezone()->getName(); + + return $return; + } + + $return->__CLASS__ = ClassUtils::getClass($var); + + if ($var instanceof Proxy) { + $return->__IS_PROXY__ = true; + $return->__PROXY_INITIALIZED__ = $var->__isInitialized(); + } + + if ($var instanceof \ArrayObject || $var instanceof \ArrayIterator) { + $return->__STORAGE__ = self::export($var->getArrayCopy(), $maxDepth - 1); + } + + return self::fillReturnWithClassAttributes($var, $return, $maxDepth); + } + + /** + * Fill the $return variable with class attributes + * + * @param object $var + * @param stdClass $return + * @param int $maxDepth + * + * @return mixed + */ + private static function fillReturnWithClassAttributes($var, \stdClass $return, $maxDepth) + { + $reflClass = ClassUtils::newReflectionObject($var); + $parsedAttributes = array(); + do { + $currentClassName = $reflClass->getName(); + + foreach ($reflClass->getProperties() as $reflProperty) { + $attributeKey = $reflProperty->isPrivate() ? $currentClassName . '#' : ''; + $attributeKey .= $reflProperty->getName(); + + if (isset($parsedAttributes[$attributeKey])) { + continue; + } + + $parsedAttributes[$attributeKey] = true; + + $name = + $reflProperty->getName() + . ($return->__CLASS__ !== $currentClassName || $reflProperty->isPrivate() ? ':' . $currentClassName : '') + . ($reflProperty->isPrivate() ? ':private' : '') + . ($reflProperty->isProtected() ? ':protected' : '') + ; + + $reflProperty->setAccessible(true); + $return->$name = self::export($reflProperty->getValue($var), $maxDepth - 1); + } + } while ($reflClass = $reflClass->getParentClass()); + + return $return; + } + + /** + * Returns a string representation of an object. + * + * @param object $obj + * + * @return string + */ + public static function toString($obj) + { + return method_exists($obj, '__toString') ? (string) $obj : get_class($obj) . '@' . spl_object_hash($obj); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Util/Inflector.php b/vendor/doctrine/common/lib/Doctrine/Common/Util/Inflector.php new file mode 100644 index 00000000..082dc789 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Util/Inflector.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\Common\Util; + +use Doctrine\Common\Inflector\Inflector as BaseInflector; + +/** + * Doctrine inflector has static methods for inflecting text. + * + * Kept for backwards compatibility reasons, was moved to its own component. + */ +class Inflector extends BaseInflector +{ +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Version.php b/vendor/doctrine/common/lib/Doctrine/Common/Version.php new file mode 100644 index 00000000..eed56555 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Version.php @@ -0,0 +1,53 @@ +. + */ + +namespace Doctrine\Common; + +/** + * Class to store and retrieve the version of Doctrine. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class Version +{ + /** + * Current Doctrine Version. + */ + const VERSION = '2.7.0-DEV'; + + /** + * Compares a Doctrine version with the current one. + * + * @param string $version Doctrine version to compare. + * + * @return int -1 if older, 0 if it is the same, 1 if version passed as argument is newer. + */ + public static function compare($version) + { + $currentVersion = str_replace(' ', '', strtolower(self::VERSION)); + $version = str_replace(' ', '', $version); + + return version_compare($version, $currentVersion); + } +} diff --git a/vendor/doctrine/dbal/LICENSE b/vendor/doctrine/dbal/LICENSE new file mode 100644 index 00000000..4a91f0bf --- /dev/null +++ b/vendor/doctrine/dbal/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2012 Doctrine Project + +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/vendor/doctrine/dbal/README.md b/vendor/doctrine/dbal/README.md new file mode 100644 index 00000000..b9956306 --- /dev/null +++ b/vendor/doctrine/dbal/README.md @@ -0,0 +1,14 @@ +# Doctrine DBAL + +Powerful database abstraction layer with many features for database schema introspection, schema management and PDO abstraction. + +* Master: [![Build Status](https://secure.travis-ci.org/doctrine/dbal.png?branch=master)](http://travis-ci.org/doctrine/dbal) [![Dependency Status](https://www.versioneye.com/php/doctrine:dbal/dev-master/badge.png)](https://www.versioneye.com/php/doctrine:dbal/dev-master) +* 2.4: [![Build Status](https://secure.travis-ci.org/doctrine/dbal.png?branch=2.4)](http://travis-ci.org/doctrine/dbal) [![Dependency Status](https://www.versioneye.com/php/doctrine:dbal/2.4.2/badge.png)](https://www.versioneye.com/php/doctrine:dbal/2.4.2) +* 2.3: [![Build Status](https://secure.travis-ci.org/doctrine/dbal.png?branch=2.3)](http://travis-ci.org/doctrine/dbal) [![Dependency Status](https://www.versioneye.com/php/doctrine:dbal/2.3.4/badge.png)](https://www.versioneye.com/php/doctrine:dbal/2.3.4) + +## More resources: + +* [Website](http://www.doctrine-project.org/projects/dbal.html) +* [Documentation](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/) +* [Issue Tracker](http://www.doctrine-project.org/jira/browse/DBAL) +* [Downloads](http://github.com/doctrine/dbal/downloads) diff --git a/vendor/doctrine/dbal/SECURITY.md b/vendor/doctrine/dbal/SECURITY.md new file mode 100644 index 00000000..e18f0dd7 --- /dev/null +++ b/vendor/doctrine/dbal/SECURITY.md @@ -0,0 +1,14 @@ +Security +======== + +The Doctrine library is operating very close to your database and as such needs +to handle and make assumptions about SQL injection vulnerabilities. + +It is vital that you understand how Doctrine approaches security, because +we cannot protect you from SQL injection. + +Please read the documentation chapter on Security in Doctrine DBAL to +understand the assumptions we make. + +- [Latest security.rst page on Github](https://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst) +- [Security Page in rendered documentation](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/security.html) diff --git a/vendor/doctrine/dbal/UPGRADE.md b/vendor/doctrine/dbal/UPGRADE.md new file mode 100644 index 00000000..1d628eef --- /dev/null +++ b/vendor/doctrine/dbal/UPGRADE.md @@ -0,0 +1,244 @@ +# Upgrade to 2.5.1 + +## MINOR BC BREAK: Doctrine\DBAL\Schema\Table + +When adding indexes to ``Doctrine\DBAL\Schema\Table`` via ``addIndex()`` or ``addUniqueIndex()``, +duplicate indexes are not silently ignored/dropped anymore (based on semantics, not naming!). +Duplicate indexes are considered indexes that pass ``isFullfilledBy()`` or ``overrules()`` +in ``Doctrine\DBAL\Schema\Index``. +This is required to make the index renaming feature introduced in 2.5.0 work properly and avoid +issues in the ORM schema tool / DBAL schema manager which pretends users from updating +their schemas and migrate to DBAL 2.5.*. +Additionally it offers more flexibility in declaring indexes for the user and potentially fixes +related issues in the ORM. +With this change, the responsibility to decide which index is a "duplicate" is completely deferred +to the user. +Please also note that adding foreign key constraints to a table via ``addForeignKeyConstraint()``, +``addUnnamedForeignKeyConstraint()`` or ``addNamedForeignKeyConstraint()`` now first checks if an +appropriate index is already present and avoids adding an additional auto-generated one eventually. + +# Upgrade to 2.5 + +## BC BREAK: time type resets date fields to UNIX epoch + +When mapping `time` type field to PHP's `DateTime` instance all unused date fields are +reset to UNIX epoch (i.e. 1970-01-01). This might break any logic which relies on comparing +`DateTime` instances with date fields set to the current date. + +Use `!` format prefix (see http://php.net/manual/en/datetime.createfromformat.php) for parsing +time strings to prevent having different date fields when comparing user input and `DateTime` +instances as mapped by Doctrine. + +## BC BREAK: Doctrine\DBAL\Schema\Table + +The methods ``addIndex()`` and ``addUniqueIndex()`` in ``Doctrine\DBAL\Schema\Table`` +have an additional, optional parameter. If you override these methods, you should +add this new parameter to the declaration of your overridden methods. + +## BC BREAK: Doctrine\DBAL\Connection + +The visibility of the property ``$_platform`` in ``Doctrine\DBAL\Connection`` +was changed from protected to private. If you have subclassed ``Doctrine\DBAL\Connection`` +in your application and accessed ``$_platform`` directly, you have to change the code +portions to use ``getDatabasePlatform()`` instead to retrieve the underlying database +platform. +The reason for this change is the new automatic platform version detection feature, +which lazily evaluates the appropriate platform class to use for the underlying database +server version at runtime. +Please also note, that calling ``getDatabasePlatform()`` now needs to establish a connection +in order to evaluate the appropriate platform class if ``Doctrine\DBAL\Connection`` is not +already connected. Under the following circumstances, it is not possible anymore to retrieve +the platform instance from the connection object without having to do a real connect: + +1. ``Doctrine\DBAL\Connection`` was instantiated without the ``platform`` connection parameter. +2. ``Doctrine\DBAL\Connection`` was instantiated without the ``serverVersion`` connection parameter. +3. The underlying driver is "version aware" and can provide different platform instances + for different versions. +4. The underlying driver connection is "version aware" and can provide the database server + version without having to query for it. + +If one of the above conditions is NOT met, there is no need for ``Doctrine\DBAL\Connection`` +to do a connect when calling ``getDatabasePlatform()``. + +## datetime Type uses date_create() as fallback + +Before 2.5 the DateTime type always required a specific format, defined in +`$platform->getDateTimeFormatString()`, which could cause quite some troubles +on platforms that had various microtime precision formats. Starting with 2.5 +whenever the parsing of a date fails with the predefined platform format, +the `date_create()` function will be used to parse the date. + +This could cause some troubles when your date format is weird and not parsed +correctly by `date_create`, however since databases are rather strict on dates +there should be no problem. + +## Support for pdo_ibm driver removed + +The ``pdo_ibm`` driver is buggy and does not work well with Doctrine. Therefore it will no +longer be supported and has been removed from the ``Doctrine\DBAL\DriverManager`` drivers +map. It is highly encouraged to to use `ibm_db2` driver instead if you want to connect +to an IBM DB2 database as it is much more stable and secure. + +If for some reason you have to utilize the ``pdo_ibm`` driver you can still use the `driverClass` +connection parameter to explicitly specify the ``Doctrine\DBAL\Driver\PDOIbm\Driver`` class. +However be aware that you are doing this at your own risk and it will not be guaranteed that +Doctrine will work as expected. + +# Upgrade to 2.4 + +## Doctrine\DBAL\Schema\Constraint + +If you have custom classes that implement the constraint interface, you have to implement +an additional method ``getQuotedColumns`` now. This method is used to build proper constraint +SQL for columns that need to be quoted, like keywords reserved by the specific platform used. +The method has to return the same values as ``getColumns`` only that those column names that +need quotation have to be returned quoted for the given platform. + +# Upgrade to 2.3 + +## Oracle Session Init now sets Numeric Character + +Before 2.3 the Oracle Session Init did not care about the numeric character of the Session. +This could lead to problems on non english locale systems that required a comma as a floating +point seperator in Oracle. Since 2.3, using the Oracle Session Init on connection start the +client session will be altered to set the numeric character to ".,": + + ALTER SESSION SET NLS_NUMERIC_CHARACTERS = '.,' + +See [DBAL-345](http://www.doctrine-project.org/jira/browse/DBAL-345) for more details. + +## Doctrine\DBAL\Connection and Doctrine\DBAL\Statement + +The query related methods including but not limited to executeQuery, exec, query, and executeUpdate +now wrap the driver exceptions such as PDOException with DBALException to add more debugging +information such as the executed SQL statement, and any bound parameters. + +If you want to retrieve the driver specific exception, you can retrieve it by calling the +``getPrevious()`` method on DBALException. + +Before: + + catch(\PDOException $ex) { + // ... + } + +After: + + catch(\Doctrine\DBAL\DBALException $ex) { + $pdoException = $ex->getPrevious(); + // ... + } + +## Doctrine\DBAL\Connection#setCharsetSQL() removed + +This method only worked on MySQL and it is considered unsafe on MySQL to use SET NAMES UTF-8 instead +of setting the charset directly on connection already. Replace this behavior with the +connection charset option: + +Before: + + $conn = DriverManager::getConnection(array(..)); + $conn->setCharset('UTF8'); + +After: + + $conn = DriverManager::getConnection(array('charset' => 'UTF8', ..)); + +## Doctrine\DBAL\Schema\Table#renameColumn() removed + +Doctrine\DBAL\Schema\Table#renameColumn() was removed, because it drops and recreates +the column instead. There is no fix available, because a schema diff +cannot reliably detect if a column was renamed or one column was created +and another one dropped. + +You should use explicit SQL ALTER TABLE statements to change columns names. + +## Schema Filter paths + +The Filter Schema assets expression is not wrapped in () anymore for the regexp automatically. + +Before: + + $config->setFilterSchemaAssetsExpression('foo'); + +After: + + $config->setFilterSchemaAssetsExpression('(foo)'); + +## Creating MySQL Tables now defaults to UTF-8 + +If you are creating a new MySQL Table through the Doctrine API, charset/collate are +now set to 'utf8'/'utf8_unicode_ci' by default. Previously the MySQL server defaults were used. + +# Upgrade to 2.2 + +## Doctrine\DBAL\Connection#insert and Doctrine\DBAL\Connection#update + +Both methods now accept an optional last parameter $types with binding types of the values passed. +This can potentially break child classes that have overwritten one of these methods. + +## Doctrine\DBAL\Connection#executeQuery + +Doctrine\DBAL\Connection#executeQuery() got a new last parameter "QueryCacheProfile $qcp" + +## Doctrine\DBAL\Driver\Statement split + +The Driver statement was split into a ResultStatement and the normal statement extending from it. +This separates the configuration and the retrieval API from a statement. + +## MsSql Platform/SchemaManager renamed + +The MsSqlPlatform was renamed to SQLServerPlatform, the MsSqlSchemaManager was renamed +to SQLServerSchemaManager. + +## Cleanup SQLServer Platform version mess + +DBAL 2.1 and before were actually only compatible to SQL Server 2008, not earlier versions. +Still other parts of the platform did use old features instead of newly introduced datatypes +in SQL Server 2005. Starting with DBAL 2.2 you can pick the Doctrine abstraction exactly +matching your SQL Server version. + +The PDO SqlSrv driver now uses the new `SQLServer2008Platform` as default platform. +This platform uses new features of SQL Server as of version 2008. This also includes a switch +in the used fields for "text" and "blob" field types to: + + "text" => "VARCHAR(MAX)" + "blob" => "VARBINARY(MAX)" + +Additionally `SQLServerPlatform` in DBAL 2.1 and before used "DATE", "TIME" and "DATETIME2" for dates. +This types are only available since version 2008 and the introduction of an explicit +SQLServer 2008 platform makes this dependency explicit. + +An `SQLServer2005Platform` was also introduced to differentiate the features between +versions 2003, earlier and 2005. + +With this change the `SQLServerPlatform` now throws an exception for using limit queries +with an offset, since SQLServer 2003 and lower do not support this feature. + +To use the old SQL Server Platform, because you are using SQL Server 2003 and below use +the following configuration code: + + use Doctrine\DBAL\DriverManager; + use Doctrine\DBAL\Platforms\SQLServerPlatform; + use Doctrine\DBAL\Platforms\SQLServer2005Platform; + + // You are using SQL Server 2003 or earlier + $conn = DriverManager::getConnection(array( + 'driver' => 'pdo_sqlsrv', + 'platform' => new SQLServerPlatform() + // .. additional parameters + )); + + // You are using SQL Server 2005 + $conn = DriverManager::getConnection(array( + 'driver' => 'pdo_sqlsrv', + 'platform' => new SQLServer2005Platform() + // .. additional parameters + )); + + // You are using SQL Server 2008 + $conn = DriverManager::getConnection(array( + 'driver' => 'pdo_sqlsrv', + // 2008 is default platform + // .. additional parameters + )); diff --git a/vendor/doctrine/dbal/bin/doctrine-dbal b/vendor/doctrine/dbal/bin/doctrine-dbal new file mode 100755 index 00000000..0531527d --- /dev/null +++ b/vendor/doctrine/dbal/bin/doctrine-dbal @@ -0,0 +1,4 @@ +#!/usr/bin/env php +. + */ + +use Symfony\Component\Console\Helper\HelperSet; +use Doctrine\DBAL\Tools\Console\ConsoleRunner; + +$files = array(__DIR__ . '/../vendor/autoload.php', __DIR__ . '/../../../autoload.php'); +$loader = null; +$cwd = getcwd(); +$directories = array($cwd, $cwd . DIRECTORY_SEPARATOR . 'config'); +$configFile = null; + +foreach ($files as $file) { + if (file_exists($file)) { + $loader = require $file; + + break; + } +} + +if ( ! $loader) { + throw new RuntimeException('vendor/autoload.php could not be found. Did you run `php composer.phar install`?'); +} + +foreach ($directories as $directory) { + $configFile = $directory . DIRECTORY_SEPARATOR . 'cli-config.php'; + + if (file_exists($configFile)) { + break; + } +} + +if ( ! file_exists($configFile)) { + ConsoleRunner::printCliConfigTemplate(); + + exit(1); +} + +if ( ! is_readable($configFile)) { + echo 'Configuration file [' . $configFile . '] does not have read permission.' . PHP_EOL; + + exit(1); +} + +$commands = array(); +$helperSet = require $configFile; + +if ( ! $helperSet instanceof HelperSet) { + foreach ($GLOBALS as $helperSetCandidate) { + if ($helperSetCandidate instanceof HelperSet) { + $helperSet = $helperSetCandidate; + + break; + } + } +} + +ConsoleRunner::run($helperSet, $commands); diff --git a/vendor/doctrine/dbal/composer.json b/vendor/doctrine/dbal/composer.json new file mode 100644 index 00000000..acf3405a --- /dev/null +++ b/vendor/doctrine/dbal/composer.json @@ -0,0 +1,37 @@ +{ + "name": "doctrine/dbal", + "type": "library", + "description": "Database Abstraction Layer", + "keywords": ["dbal", "database", "persistence", "queryobject"], + "homepage": "http://www.doctrine-project.org", + "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"} + ], + "require": { + "php": ">=5.3.2", + "doctrine/common": ">=2.4,<2.8-dev" + }, + "require-dev": { + "phpunit/phpunit": "4.*", + "symfony/console": "2.*||^3.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": ["bin/doctrine-dbal"], + "autoload": { + "psr-0": { "Doctrine\\DBAL\\": "lib/" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.5.x-dev" + } + }, + "archive": { + "exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar"] + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/ArrayStatement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/ArrayStatement.php new file mode 100644 index 00000000..0752dda3 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/ArrayStatement.php @@ -0,0 +1,148 @@ +. + */ + +namespace Doctrine\DBAL\Cache; + +use Doctrine\DBAL\Driver\ResultStatement; +use PDO; + +class ArrayStatement implements \IteratorAggregate, ResultStatement +{ + /** + * @var array + */ + private $data; + + /** + * @var integer + */ + private $columnCount = 0; + + /** + * @var integer + */ + private $num = 0; + + /** + * @var integer + */ + private $defaultFetchMode = PDO::FETCH_BOTH; + + /** + * @param array $data + */ + public function __construct(array $data) + { + $this->data = $data; + if (count($data)) { + $this->columnCount = count($data[0]); + } + } + + /** + * {@inheritdoc} + */ + public function closeCursor() + { + unset ($this->data); + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return $this->columnCount; + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + if ($arg2 !== null || $arg3 !== null) { + throw new \InvalidArgumentException("Caching layer does not support 2nd/3rd argument to setFetchMode()"); + } + + $this->defaultFetchMode = $fetchMode; + + return true; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $data = $this->fetchAll(); + + return new \ArrayIterator($data); + } + + /** + * {@inheritdoc} + */ + public function fetch($fetchMode = null) + { + if (isset($this->data[$this->num])) { + $row = $this->data[$this->num++]; + $fetchMode = $fetchMode ?: $this->defaultFetchMode; + if ($fetchMode === PDO::FETCH_ASSOC) { + return $row; + } elseif ($fetchMode === PDO::FETCH_NUM) { + return array_values($row); + } elseif ($fetchMode === PDO::FETCH_BOTH) { + return array_merge($row, array_values($row)); + } elseif ($fetchMode === PDO::FETCH_COLUMN) { + return reset($row); + } else { + throw new \InvalidArgumentException("Invalid fetch-style given for fetching result."); + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null) + { + $rows = array(); + while ($row = $this->fetch($fetchMode)) { + $rows[] = $row; + } + + return $rows; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(PDO::FETCH_NUM); + if (!isset($row[$columnIndex])) { + // TODO: verify this is correct behavior + return false; + } + + return $row[$columnIndex]; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/CacheException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/CacheException.php new file mode 100644 index 00000000..6b3b5394 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/CacheException.php @@ -0,0 +1,43 @@ +. + */ + +namespace Doctrine\DBAL\Cache; + +/** + * @author Benjamin Eberlei + * @since 2.2 + */ +class CacheException extends \Doctrine\DBAL\DBALException +{ + /** + * @return \Doctrine\DBAL\Cache\CacheException + */ + static public function noCacheKey() + { + return new self("No cache key was set."); + } + + /** + * @return \Doctrine\DBAL\Cache\CacheException + */ + static public function noResultDriverConfigured() + { + return new self("Trying to cache a query but no result driver is configured."); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/QueryCacheProfile.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/QueryCacheProfile.php new file mode 100644 index 00000000..330eec91 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/QueryCacheProfile.php @@ -0,0 +1,141 @@ +. + */ + +namespace Doctrine\DBAL\Cache; + +use Doctrine\Common\Cache\Cache; + +/** + * Query Cache Profile handles the data relevant for query caching. + * + * It is a value object, setter methods return NEW instances. + * + * @author Benjamin Eberlei + */ +class QueryCacheProfile +{ + /** + * @var \Doctrine\Common\Cache\Cache|null + */ + private $resultCacheDriver; + + /** + * @var integer + */ + private $lifetime = 0; + + /** + * @var string|null + */ + private $cacheKey; + + /** + * @param integer $lifetime + * @param string|null $cacheKey + * @param \Doctrine\Common\Cache\Cache|null $resultCache + */ + public function __construct($lifetime = 0, $cacheKey = null, Cache $resultCache = null) + { + $this->lifetime = $lifetime; + $this->cacheKey = $cacheKey; + $this->resultCacheDriver = $resultCache; + } + + /** + * @return \Doctrine\Common\Cache\Cache|null + */ + public function getResultCacheDriver() + { + return $this->resultCacheDriver; + } + + /** + * @return integer + */ + public function getLifetime() + { + return $this->lifetime; + } + + /** + * @return string + * + * @throws \Doctrine\DBAL\Cache\CacheException + */ + public function getCacheKey() + { + if ($this->cacheKey === null) { + throw CacheException::noCacheKey(); + } + + return $this->cacheKey; + } + + /** + * Generates the real cache key from query, params and types. + * + * @param string $query + * @param array $params + * @param array $types + * + * @return array + */ + public function generateCacheKeys($query, $params, $types) + { + $realCacheKey = $query . "-" . serialize($params) . "-" . serialize($types); + // should the key be automatically generated using the inputs or is the cache key set? + if ($this->cacheKey === null) { + $cacheKey = sha1($realCacheKey); + } else { + $cacheKey = $this->cacheKey; + } + + return array($cacheKey, $realCacheKey); + } + + /** + * @param \Doctrine\Common\Cache\Cache $cache + * + * @return \Doctrine\DBAL\Cache\QueryCacheProfile + */ + public function setResultCacheDriver(Cache $cache) + { + return new QueryCacheProfile($this->lifetime, $this->cacheKey, $cache); + } + + /** + * @param string|null $cacheKey + * + * @return \Doctrine\DBAL\Cache\QueryCacheProfile + */ + public function setCacheKey($cacheKey) + { + return new QueryCacheProfile($this->lifetime, $cacheKey, $this->resultCacheDriver); + } + + /** + * @param integer $lifetime + * + * @return \Doctrine\DBAL\Cache\QueryCacheProfile + */ + public function setLifetime($lifetime) + { + return new QueryCacheProfile($lifetime, $this->cacheKey, $this->resultCacheDriver); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php new file mode 100644 index 00000000..0062255e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php @@ -0,0 +1,221 @@ +. + */ + +namespace Doctrine\DBAL\Cache; + +use Doctrine\DBAL\Driver\Statement; +use Doctrine\DBAL\Driver\ResultStatement; +use Doctrine\Common\Cache\Cache; +use PDO; + +/** + * Cache statement for SQL results. + * + * A result is saved in multiple cache keys, there is the originally specified + * cache key which is just pointing to result rows by key. The following things + * have to be ensured: + * + * 1. lifetime of the original key has to be longer than that of all the individual rows keys + * 2. if any one row key is missing the query has to be re-executed. + * + * Also you have to realize that the cache will load the whole result into memory at once to ensure 2. + * This means that the memory usage for cached results might increase by using this feature. + */ +class ResultCacheStatement implements \IteratorAggregate, ResultStatement +{ + /** + * @var \Doctrine\Common\Cache\Cache + */ + private $resultCache; + + /** + * + * @var string + */ + private $cacheKey; + + /** + * @var string + */ + private $realKey; + + /** + * @var integer + */ + private $lifetime; + + /** + * @var \Doctrine\DBAL\Driver\Statement + */ + private $statement; + + /** + * Did we reach the end of the statement? + * + * @var boolean + */ + private $emptied = false; + + /** + * @var array + */ + private $data; + + /** + * @var integer + */ + private $defaultFetchMode = PDO::FETCH_BOTH; + + /** + * @param \Doctrine\DBAL\Driver\Statement $stmt + * @param \Doctrine\Common\Cache\Cache $resultCache + * @param string $cacheKey + * @param string $realKey + * @param integer $lifetime + */ + public function __construct(Statement $stmt, Cache $resultCache, $cacheKey, $realKey, $lifetime) + { + $this->statement = $stmt; + $this->resultCache = $resultCache; + $this->cacheKey = $cacheKey; + $this->realKey = $realKey; + $this->lifetime = $lifetime; + } + + /** + * {@inheritdoc} + */ + public function closeCursor() + { + $this->statement->closeCursor(); + if ($this->emptied && $this->data !== null) { + $data = $this->resultCache->fetch($this->cacheKey); + if ( ! $data) { + $data = array(); + } + $data[$this->realKey] = $this->data; + + $this->resultCache->save($this->cacheKey, $data, $this->lifetime); + unset($this->data); + } + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return $this->statement->columnCount(); + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->defaultFetchMode = $fetchMode; + + return true; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $data = $this->fetchAll(); + + return new \ArrayIterator($data); + } + + /** + * {@inheritdoc} + */ + public function fetch($fetchMode = null) + { + if ($this->data === null) { + $this->data = array(); + } + + $row = $this->statement->fetch(PDO::FETCH_ASSOC); + if ($row) { + $this->data[] = $row; + + $fetchMode = $fetchMode ?: $this->defaultFetchMode; + + if ($fetchMode == PDO::FETCH_ASSOC) { + return $row; + } elseif ($fetchMode == PDO::FETCH_NUM) { + return array_values($row); + } elseif ($fetchMode == PDO::FETCH_BOTH) { + return array_merge($row, array_values($row)); + } elseif ($fetchMode == PDO::FETCH_COLUMN) { + return reset($row); + } else { + throw new \InvalidArgumentException("Invalid fetch-style given for caching result."); + } + } + $this->emptied = true; + + return false; + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null) + { + $rows = array(); + while ($row = $this->fetch($fetchMode)) { + $rows[] = $row; + } + + return $rows; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(PDO::FETCH_NUM); + if (!isset($row[$columnIndex])) { + // TODO: verify this is correct behavior + return false; + } + + return $row[$columnIndex]; + } + + /** + * Returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement + * executed by the corresponding object. + * + * If the last SQL statement executed by the associated Statement object was a SELECT statement, + * some databases may return the number of rows returned by that statement. However, + * this behaviour is not guaranteed for all databases and should not be + * relied on for portable applications. + * + * @return integer The number of rows. + */ + public function rowCount() + { + return $this->statement->rowCount(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Configuration.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Configuration.php new file mode 100644 index 00000000..ec7c1306 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Configuration.php @@ -0,0 +1,152 @@ +. + */ + +namespace Doctrine\DBAL; + +use Doctrine\DBAL\Logging\SQLLogger; +use Doctrine\Common\Cache\Cache; + +/** + * Configuration container for the Doctrine DBAL. + * + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @internal When adding a new configuration option just write a getter/setter + * pair and add the option to the _attributes array with a proper default value. + */ +class Configuration +{ + /** + * The attributes that are contained in the configuration. + * Values are default values. + * + * @var array + */ + protected $_attributes = array(); + + /** + * Sets the SQL logger to use. Defaults to NULL which means SQL logging is disabled. + * + * @param \Doctrine\DBAL\Logging\SQLLogger|null $logger + * + * @return void + */ + public function setSQLLogger(SQLLogger $logger = null) + { + $this->_attributes['sqlLogger'] = $logger; + } + + /** + * Gets the SQL logger that is used. + * + * @return \Doctrine\DBAL\Logging\SQLLogger|null + */ + public function getSQLLogger() + { + return isset($this->_attributes['sqlLogger']) ? + $this->_attributes['sqlLogger'] : null; + } + + /** + * Gets the cache driver implementation that is used for query result caching. + * + * @return \Doctrine\Common\Cache\Cache|null + */ + public function getResultCacheImpl() + { + return isset($this->_attributes['resultCacheImpl']) ? + $this->_attributes['resultCacheImpl'] : null; + } + + /** + * Sets the cache driver implementation that is used for query result caching. + * + * @param \Doctrine\Common\Cache\Cache $cacheImpl + * + * @return void + */ + public function setResultCacheImpl(Cache $cacheImpl) + { + $this->_attributes['resultCacheImpl'] = $cacheImpl; + } + + /** + * Sets the filter schema assets expression. + * + * Only include tables/sequences matching the filter expression regexp in + * schema instances generated for the active connection when calling + * {AbstractSchemaManager#createSchema()}. + * + * @param string $filterExpression + * + * @return void + */ + public function setFilterSchemaAssetsExpression($filterExpression) + { + $this->_attributes['filterSchemaAssetsExpression'] = $filterExpression; + } + + /** + * Returns filter schema assets expression. + * + * @return string|null + */ + public function getFilterSchemaAssetsExpression() + { + if (isset($this->_attributes['filterSchemaAssetsExpression'])) { + return $this->_attributes['filterSchemaAssetsExpression']; + } + + return null; + } + + /** + * Sets the default auto-commit mode for connections. + * + * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual + * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either + * the method commit or the method rollback. By default, new connections are in auto-commit mode. + * + * @param boolean $autoCommit True to enable auto-commit mode; false to disable it. + * + * @see getAutoCommit + */ + public function setAutoCommit($autoCommit) + { + $this->_attributes['autoCommit'] = (boolean) $autoCommit; + } + + /** + * Returns the default auto-commit mode for connections. + * + * @return boolean True if auto-commit mode is enabled by default for connections, false otherwise. + * + * @see setAutoCommit + */ + public function getAutoCommit() + { + if (isset($this->_attributes['autoCommit'])) { + return $this->_attributes['autoCommit']; + } + + return true; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php new file mode 100644 index 00000000..7e33c243 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php @@ -0,0 +1,1626 @@ +. + */ + +namespace Doctrine\DBAL; + +use Doctrine\DBAL\Driver\ServerInfoAwareConnection; +use Doctrine\DBAL\Exception\InvalidArgumentException; +use PDO; +use Closure; +use Exception; +use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Driver\Connection as DriverConnection; +use Doctrine\Common\EventManager; +use Doctrine\DBAL\Cache\ResultCacheStatement; +use Doctrine\DBAL\Cache\QueryCacheProfile; +use Doctrine\DBAL\Cache\ArrayStatement; +use Doctrine\DBAL\Cache\CacheException; +use Doctrine\DBAL\Driver\PingableConnection; + +/** + * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like + * events, transaction isolation levels, configuration, emulated transaction nesting, + * lazy connecting and more. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Konsta Vesterinen + * @author Lukas Smith (MDB2 library) + * @author Benjamin Eberlei + */ +class Connection implements DriverConnection +{ + /** + * Constant for transaction isolation level READ UNCOMMITTED. + */ + const TRANSACTION_READ_UNCOMMITTED = 1; + + /** + * Constant for transaction isolation level READ COMMITTED. + */ + const TRANSACTION_READ_COMMITTED = 2; + + /** + * Constant for transaction isolation level REPEATABLE READ. + */ + const TRANSACTION_REPEATABLE_READ = 3; + + /** + * Constant for transaction isolation level SERIALIZABLE. + */ + const TRANSACTION_SERIALIZABLE = 4; + + /** + * Represents an array of ints to be expanded by Doctrine SQL parsing. + * + * @var integer + */ + const PARAM_INT_ARRAY = 101; + + /** + * Represents an array of strings to be expanded by Doctrine SQL parsing. + * + * @var integer + */ + const PARAM_STR_ARRAY = 102; + + /** + * Offset by which PARAM_* constants are detected as arrays of the param type. + * + * @var integer + */ + const ARRAY_PARAM_OFFSET = 100; + + /** + * The wrapped driver connection. + * + * @var \Doctrine\DBAL\Driver\Connection + */ + protected $_conn; + + /** + * @var \Doctrine\DBAL\Configuration + */ + protected $_config; + + /** + * @var \Doctrine\Common\EventManager + */ + protected $_eventManager; + + /** + * @var \Doctrine\DBAL\Query\Expression\ExpressionBuilder + */ + protected $_expr; + + /** + * Whether or not a connection has been established. + * + * @var boolean + */ + private $_isConnected = false; + + /** + * The current auto-commit mode of this connection. + * + * @var boolean + */ + private $autoCommit = true; + + /** + * The transaction nesting level. + * + * @var integer + */ + private $_transactionNestingLevel = 0; + + /** + * The currently active transaction isolation level. + * + * @var integer + */ + private $_transactionIsolationLevel; + + /** + * If nested transactions should use savepoints. + * + * @var boolean + */ + private $_nestTransactionsWithSavepoints = false; + + /** + * The parameters used during creation of the Connection instance. + * + * @var array + */ + private $_params = array(); + + /** + * The DatabasePlatform object that provides information about the + * database platform used by the connection. + * + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $platform; + + /** + * The schema manager. + * + * @var \Doctrine\DBAL\Schema\AbstractSchemaManager + */ + protected $_schemaManager; + + /** + * The used DBAL driver. + * + * @var \Doctrine\DBAL\Driver + */ + protected $_driver; + + /** + * Flag that indicates whether the current transaction is marked for rollback only. + * + * @var boolean + */ + private $_isRollbackOnly = false; + + /** + * @var integer + */ + protected $defaultFetchMode = PDO::FETCH_ASSOC; + + /** + * Initializes a new instance of the Connection class. + * + * @param array $params The connection parameters. + * @param \Doctrine\DBAL\Driver $driver The driver to use. + * @param \Doctrine\DBAL\Configuration|null $config The configuration, optional. + * @param \Doctrine\Common\EventManager|null $eventManager The event manager, optional. + * + * @throws \Doctrine\DBAL\DBALException + */ + public function __construct(array $params, Driver $driver, Configuration $config = null, + EventManager $eventManager = null) + { + $this->_driver = $driver; + $this->_params = $params; + + if (isset($params['pdo'])) { + $this->_conn = $params['pdo']; + $this->_isConnected = true; + unset($this->_params['pdo']); + } + + // Create default config and event manager if none given + if ( ! $config) { + $config = new Configuration(); + } + + if ( ! $eventManager) { + $eventManager = new EventManager(); + } + + $this->_config = $config; + $this->_eventManager = $eventManager; + + $this->_expr = new Query\Expression\ExpressionBuilder($this); + + $this->autoCommit = $config->getAutoCommit(); + } + + /** + * Gets the parameters used during instantiation. + * + * @return array + */ + public function getParams() + { + return $this->_params; + } + + /** + * Gets the name of the database this Connection is connected to. + * + * @return string + */ + public function getDatabase() + { + return $this->_driver->getDatabase($this); + } + + /** + * Gets the hostname of the currently connected database. + * + * @return string|null + */ + public function getHost() + { + return isset($this->_params['host']) ? $this->_params['host'] : null; + } + + /** + * Gets the port of the currently connected database. + * + * @return mixed + */ + public function getPort() + { + return isset($this->_params['port']) ? $this->_params['port'] : null; + } + + /** + * Gets the username used by this connection. + * + * @return string|null + */ + public function getUsername() + { + return isset($this->_params['user']) ? $this->_params['user'] : null; + } + + /** + * Gets the password used by this connection. + * + * @return string|null + */ + public function getPassword() + { + return isset($this->_params['password']) ? $this->_params['password'] : null; + } + + /** + * Gets the DBAL driver instance. + * + * @return \Doctrine\DBAL\Driver + */ + public function getDriver() + { + return $this->_driver; + } + + /** + * Gets the Configuration used by the Connection. + * + * @return \Doctrine\DBAL\Configuration + */ + public function getConfiguration() + { + return $this->_config; + } + + /** + * Gets the EventManager used by the Connection. + * + * @return \Doctrine\Common\EventManager + */ + public function getEventManager() + { + return $this->_eventManager; + } + + /** + * Gets the DatabasePlatform for the connection. + * + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getDatabasePlatform() + { + if (null == $this->platform) { + $this->detectDatabasePlatform(); + } + + return $this->platform; + } + + /** + * Gets the ExpressionBuilder for the connection. + * + * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder + */ + public function getExpressionBuilder() + { + return $this->_expr; + } + + /** + * Establishes the connection with the database. + * + * @return boolean TRUE if the connection was successfully established, FALSE if + * the connection is already open. + */ + public function connect() + { + if ($this->_isConnected) return false; + + $driverOptions = isset($this->_params['driverOptions']) ? + $this->_params['driverOptions'] : array(); + $user = isset($this->_params['user']) ? $this->_params['user'] : null; + $password = isset($this->_params['password']) ? + $this->_params['password'] : null; + + $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions); + $this->_isConnected = true; + + if (null === $this->platform) { + $this->detectDatabasePlatform(); + } + + if (false === $this->autoCommit) { + $this->beginTransaction(); + } + + if ($this->_eventManager->hasListeners(Events::postConnect)) { + $eventArgs = new Event\ConnectionEventArgs($this); + $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); + } + + return true; + } + + /** + * Detects and sets the database platform. + * + * Evaluates custom platform class and version in order to set the correct platform. + * + * @throws DBALException if an invalid platform was specified for this connection. + */ + private function detectDatabasePlatform() + { + if ( ! isset($this->_params['platform'])) { + $version = $this->getDatabasePlatformVersion(); + + if (null !== $version) { + $this->platform = $this->_driver->createDatabasePlatformForVersion($version); + } else { + $this->platform = $this->_driver->getDatabasePlatform(); + } + } elseif ($this->_params['platform'] instanceof Platforms\AbstractPlatform) { + $this->platform = $this->_params['platform']; + } else { + throw DBALException::invalidPlatformSpecified(); + } + + $this->platform->setEventManager($this->_eventManager); + } + + /** + * Returns the version of the related platform if applicable. + * + * Returns null if either the driver is not capable to create version + * specific platform instances, no explicit server version was specified + * or the underlying driver connection cannot determine the platform + * version without having to query it (performance reasons). + * + * @return string|null + */ + private function getDatabasePlatformVersion() + { + // Driver does not support version specific platforms. + if ( ! $this->_driver instanceof VersionAwarePlatformDriver) { + return null; + } + + // Explicit platform version requested (supersedes auto-detection). + if (isset($this->_params['serverVersion'])) { + return $this->_params['serverVersion']; + } + + // If not connected, we need to connect now to determine the platform version. + if (null === $this->_conn) { + $this->connect(); + } + + // Automatic platform version detection. + if ($this->_conn instanceof ServerInfoAwareConnection && + ! $this->_conn->requiresQueryForServerVersion() + ) { + return $this->_conn->getServerVersion(); + } + + // Unable to detect platform version. + return null; + } + + /** + * Returns the current auto-commit mode for this connection. + * + * @return boolean True if auto-commit mode is currently enabled for this connection, false otherwise. + * + * @see setAutoCommit + */ + public function isAutoCommit() + { + return true === $this->autoCommit; + } + + /** + * Sets auto-commit mode for this connection. + * + * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual + * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either + * the method commit or the method rollback. By default, new connections are in auto-commit mode. + * + * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is + * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op. + * + * @param boolean $autoCommit True to enable auto-commit mode; false to disable it. + * + * @see isAutoCommit + */ + public function setAutoCommit($autoCommit) + { + $autoCommit = (boolean) $autoCommit; + + // Mode not changed, no-op. + if ($autoCommit === $this->autoCommit) { + return; + } + + $this->autoCommit = $autoCommit; + + // Commit all currently active transactions if any when switching auto-commit mode. + if (true === $this->_isConnected && 0 !== $this->_transactionNestingLevel) { + $this->commitAll(); + } + } + + /** + * Sets the fetch mode. + * + * @param integer $fetchMode + * + * @return void + */ + public function setFetchMode($fetchMode) + { + $this->defaultFetchMode = $fetchMode; + } + + /** + * Prepares and executes an SQL query and returns the first row of the result + * as an associative array. + * + * @param string $statement The SQL query. + * @param array $params The query parameters. + * @param array $types The query parameter types. + * + * @return array + */ + public function fetchAssoc($statement, array $params = array(), array $types = array()) + { + return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_ASSOC); + } + + /** + * Prepares and executes an SQL query and returns the first row of the result + * as a numerically indexed array. + * + * @param string $statement The SQL query to be executed. + * @param array $params The prepared statement params. + * @param array $types The query parameter types. + * + * @return array + */ + public function fetchArray($statement, array $params = array(), array $types = array()) + { + return $this->executeQuery($statement, $params, $types)->fetch(PDO::FETCH_NUM); + } + + /** + * Prepares and executes an SQL query and returns the value of a single column + * of the first row of the result. + * + * @param string $statement The SQL query to be executed. + * @param array $params The prepared statement params. + * @param integer $column The 0-indexed column number to retrieve. + * @param array $types The query parameter types. + * + * @return mixed + */ + public function fetchColumn($statement, array $params = array(), $column = 0, array $types = array()) + { + return $this->executeQuery($statement, $params, $types)->fetchColumn($column); + } + + /** + * Whether an actual connection to the database is established. + * + * @return boolean + */ + public function isConnected() + { + return $this->_isConnected; + } + + /** + * Checks whether a transaction is currently active. + * + * @return boolean TRUE if a transaction is currently active, FALSE otherwise. + */ + public function isTransactionActive() + { + return $this->_transactionNestingLevel > 0; + } + + /** + * Executes an SQL DELETE statement on a table. + * + * Table expression and columns are not escaped and are not safe for user-input. + * + * @param string $tableExpression The expression of the table on which to delete. + * @param array $identifier The deletion criteria. An associative array containing column-value pairs. + * @param array $types The types of identifiers. + * + * @return integer The number of affected rows. + * + * @throws InvalidArgumentException + */ + public function delete($tableExpression, array $identifier, array $types = array()) + { + if (empty($identifier)) { + throw InvalidArgumentException::fromEmptyCriteria(); + } + + $columnList = array(); + $criteria = array(); + $paramValues = array(); + + foreach ($identifier as $columnName => $value) { + $columnList[] = $columnName; + $criteria[] = $columnName . ' = ?'; + $paramValues[] = $value; + } + + return $this->executeUpdate( + 'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $criteria), + $paramValues, + is_string(key($types)) ? $this->extractTypeValues($columnList, $types) : $types + ); + } + + /** + * Closes the connection. + * + * @return void + */ + public function close() + { + $this->_conn = null; + + $this->_isConnected = false; + } + + /** + * Sets the transaction isolation level. + * + * @param integer $level The level to set. + * + * @return integer + */ + public function setTransactionIsolation($level) + { + $this->_transactionIsolationLevel = $level; + + return $this->executeUpdate($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level)); + } + + /** + * Gets the currently active transaction isolation level. + * + * @return integer The current transaction isolation level. + */ + public function getTransactionIsolation() + { + if (null === $this->_transactionIsolationLevel) { + $this->_transactionIsolationLevel = $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel(); + } + + return $this->_transactionIsolationLevel; + } + + /** + * Executes an SQL UPDATE statement on a table. + * + * Table expression and columns are not escaped and are not safe for user-input. + * + * @param string $tableExpression The expression of the table to update quoted or unquoted. + * @param array $data An associative array containing column-value pairs. + * @param array $identifier The update criteria. An associative array containing column-value pairs. + * @param array $types Types of the merged $data and $identifier arrays in that order. + * + * @return integer The number of affected rows. + */ + public function update($tableExpression, array $data, array $identifier, array $types = array()) + { + $columnList = array(); + $set = array(); + $criteria = array(); + $paramValues = array(); + + foreach ($data as $columnName => $value) { + $columnList[] = $columnName; + $set[] = $columnName . ' = ?'; + $paramValues[] = $value; + } + + foreach ($identifier as $columnName => $value) { + $columnList[] = $columnName; + $criteria[] = $columnName . ' = ?'; + $paramValues[] = $value; + } + + if (is_string(key($types))) { + $types = $this->extractTypeValues($columnList, $types); + } + + $sql = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set) + . ' WHERE ' . implode(' AND ', $criteria); + + return $this->executeUpdate($sql, $paramValues, $types); + } + + /** + * Inserts a table row with specified data. + * + * Table expression and columns are not escaped and are not safe for user-input. + * + * @param string $tableExpression The expression of the table to insert data into, quoted or unquoted. + * @param array $data An associative array containing column-value pairs. + * @param array $types Types of the inserted data. + * + * @return integer The number of affected rows. + */ + public function insert($tableExpression, array $data, array $types = array()) + { + $this->connect(); + + if (empty($data)) { + return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' ()' . ' VALUES ()'); + } + + $columnList = array(); + $paramPlaceholders = array(); + $paramValues = array(); + + foreach ($data as $columnName => $value) { + $columnList[] = $columnName; + $paramPlaceholders[] = '?'; + $paramValues[] = $value; + } + + return $this->executeUpdate( + 'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columnList) . ')' . + ' VALUES (' . implode(', ', $paramPlaceholders) . ')', + $paramValues, + is_string(key($types)) ? $this->extractTypeValues($columnList, $types) : $types + ); + } + + /** + * Extract ordered type list from an ordered column list and type map. + * + * @param array $columnList + * @param array $types + * + * @return array + */ + private function extractTypeValues(array $columnList, array $types) + { + $typeValues = array(); + + foreach ($columnList as $columnIndex => $columnName) { + $typeValues[] = isset($types[$columnName]) + ? $types[$columnName] + : \PDO::PARAM_STR; + } + + return $typeValues; + } + + /** + * Quotes a string so it can be safely used as a table or column name, even if + * it is a reserved name. + * + * Delimiting style depends on the underlying database platform that is being used. + * + * NOTE: Just because you CAN use quoted identifiers does not mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. + * + * @param string $str The name to be quoted. + * + * @return string The quoted name. + */ + public function quoteIdentifier($str) + { + return $this->getDatabasePlatform()->quoteIdentifier($str); + } + + /** + * Quotes a given input parameter. + * + * @param mixed $input The parameter to be quoted. + * @param string|null $type The type of the parameter. + * + * @return string The quoted parameter. + */ + public function quote($input, $type = null) + { + $this->connect(); + + list($value, $bindingType) = $this->getBindingInfo($input, $type); + return $this->_conn->quote($value, $bindingType); + } + + /** + * Prepares and executes an SQL query and returns the result as an associative array. + * + * @param string $sql The SQL query. + * @param array $params The query parameters. + * @param array $types The query parameter types. + * + * @return array + */ + public function fetchAll($sql, array $params = array(), $types = array()) + { + return $this->executeQuery($sql, $params, $types)->fetchAll(); + } + + /** + * Prepares an SQL statement. + * + * @param string $statement The SQL statement to prepare. + * + * @return \Doctrine\DBAL\Driver\Statement The prepared statement. + * + * @throws \Doctrine\DBAL\DBALException + */ + public function prepare($statement) + { + $this->connect(); + + try { + $stmt = new Statement($statement, $this); + } catch (\Exception $ex) { + throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement); + } + + $stmt->setFetchMode($this->defaultFetchMode); + + return $stmt; + } + + /** + * Executes an, optionally parametrized, SQL query. + * + * If the query is parametrized, a prepared statement is used. + * If an SQLLogger is configured, the execution is logged. + * + * @param string $query The SQL query to execute. + * @param array $params The parameters to bind to the query, if any. + * @param array $types The types the previous parameters are in. + * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp The query cache profile, optional. + * + * @return \Doctrine\DBAL\Driver\Statement The executed statement. + * + * @throws \Doctrine\DBAL\DBALException + */ + public function executeQuery($query, array $params = array(), $types = array(), QueryCacheProfile $qcp = null) + { + if ($qcp !== null) { + return $this->executeCacheQuery($query, $params, $types, $qcp); + } + + $this->connect(); + + $logger = $this->_config->getSQLLogger(); + if ($logger) { + $logger->startQuery($query, $params, $types); + } + + try { + if ($params) { + list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types); + + $stmt = $this->_conn->prepare($query); + if ($types) { + $this->_bindTypedValues($stmt, $params, $types); + $stmt->execute(); + } else { + $stmt->execute($params); + } + } else { + $stmt = $this->_conn->query($query); + } + } catch (\Exception $ex) { + throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types)); + } + + $stmt->setFetchMode($this->defaultFetchMode); + + if ($logger) { + $logger->stopQuery(); + } + + return $stmt; + } + + /** + * Executes a caching query. + * + * @param string $query The SQL query to execute. + * @param array $params The parameters to bind to the query, if any. + * @param array $types The types the previous parameters are in. + * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp The query cache profile. + * + * @return \Doctrine\DBAL\Driver\ResultStatement + * + * @throws \Doctrine\DBAL\Cache\CacheException + */ + public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp) + { + $resultCache = $qcp->getResultCacheDriver() ?: $this->_config->getResultCacheImpl(); + if ( ! $resultCache) { + throw CacheException::noResultDriverConfigured(); + } + + list($cacheKey, $realKey) = $qcp->generateCacheKeys($query, $params, $types); + + // fetch the row pointers entry + if ($data = $resultCache->fetch($cacheKey)) { + // is the real key part of this row pointers map or is the cache only pointing to other cache keys? + if (isset($data[$realKey])) { + $stmt = new ArrayStatement($data[$realKey]); + } elseif (array_key_exists($realKey, $data)) { + $stmt = new ArrayStatement(array()); + } + } + + if (!isset($stmt)) { + $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime()); + } + + $stmt->setFetchMode($this->defaultFetchMode); + + return $stmt; + } + + /** + * Executes an, optionally parametrized, SQL query and returns the result, + * applying a given projection/transformation function on each row of the result. + * + * @param string $query The SQL query to execute. + * @param array $params The parameters, if any. + * @param \Closure $function The transformation function that is applied on each row. + * The function receives a single parameter, an array, that + * represents a row of the result set. + * + * @return array The projected result of the query. + */ + public function project($query, array $params, Closure $function) + { + $result = array(); + $stmt = $this->executeQuery($query, $params); + + while ($row = $stmt->fetch()) { + $result[] = $function($row); + } + + $stmt->closeCursor(); + + return $result; + } + + /** + * Executes an SQL statement, returning a result set as a Statement object. + * + * @return \Doctrine\DBAL\Driver\Statement + * + * @throws \Doctrine\DBAL\DBALException + */ + public function query() + { + $this->connect(); + + $args = func_get_args(); + + $logger = $this->_config->getSQLLogger(); + if ($logger) { + $logger->startQuery($args[0]); + } + + try { + switch (func_num_args()) { + case 1: + $statement = $this->_conn->query($args[0]); + break; + case 2: + $statement = $this->_conn->query($args[0], $args[1]); + break; + default: + $statement = call_user_func_array(array($this->_conn, 'query'), $args); + break; + } + } catch (\Exception $ex) { + throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $args[0]); + } + + $statement->setFetchMode($this->defaultFetchMode); + + if ($logger) { + $logger->stopQuery(); + } + + return $statement; + } + + /** + * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters + * and returns the number of affected rows. + * + * This method supports PDO binding types as well as DBAL mapping types. + * + * @param string $query The SQL query. + * @param array $params The query parameters. + * @param array $types The parameter types. + * + * @return integer The number of affected rows. + * + * @throws \Doctrine\DBAL\DBALException + */ + public function executeUpdate($query, array $params = array(), array $types = array()) + { + $this->connect(); + + $logger = $this->_config->getSQLLogger(); + if ($logger) { + $logger->startQuery($query, $params, $types); + } + + try { + if ($params) { + list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types); + + $stmt = $this->_conn->prepare($query); + if ($types) { + $this->_bindTypedValues($stmt, $params, $types); + $stmt->execute(); + } else { + $stmt->execute($params); + } + $result = $stmt->rowCount(); + } else { + $result = $this->_conn->exec($query); + } + } catch (\Exception $ex) { + throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types)); + } + + if ($logger) { + $logger->stopQuery(); + } + + return $result; + } + + /** + * Executes an SQL statement and return the number of affected rows. + * + * @param string $statement + * + * @return integer The number of affected rows. + * + * @throws \Doctrine\DBAL\DBALException + */ + public function exec($statement) + { + $this->connect(); + + $logger = $this->_config->getSQLLogger(); + if ($logger) { + $logger->startQuery($statement); + } + + try { + $result = $this->_conn->exec($statement); + } catch (\Exception $ex) { + throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement); + } + + if ($logger) { + $logger->stopQuery(); + } + + return $result; + } + + /** + * Returns the current transaction nesting level. + * + * @return integer The nesting level. A value of 0 means there's no active transaction. + */ + public function getTransactionNestingLevel() + { + return $this->_transactionNestingLevel; + } + + /** + * Fetches the SQLSTATE associated with the last database operation. + * + * @return integer The last error code. + */ + public function errorCode() + { + $this->connect(); + + return $this->_conn->errorCode(); + } + + /** + * Fetches extended error information associated with the last database operation. + * + * @return array The last error information. + */ + public function errorInfo() + { + $this->connect(); + + return $this->_conn->errorInfo(); + } + + /** + * Returns the ID of the last inserted row, or the last value from a sequence object, + * depending on the underlying driver. + * + * Note: This method may not return a meaningful or consistent result across different drivers, + * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY + * columns or sequences. + * + * @param string|null $seqName Name of the sequence object from which the ID should be returned. + * + * @return string A string representation of the last inserted ID. + */ + public function lastInsertId($seqName = null) + { + $this->connect(); + + return $this->_conn->lastInsertId($seqName); + } + + /** + * Executes a function in a transaction. + * + * The function gets passed this Connection instance as an (optional) parameter. + * + * If an exception occurs during execution of the function or transaction commit, + * the transaction is rolled back and the exception re-thrown. + * + * @param \Closure $func The function to execute transactionally. + * + * @return void + * + * @throws \Exception + */ + public function transactional(Closure $func) + { + $this->beginTransaction(); + try { + $func($this); + $this->commit(); + } catch (Exception $e) { + $this->rollback(); + throw $e; + } + } + + /** + * Sets if nested transactions should use savepoints. + * + * @param boolean $nestTransactionsWithSavepoints + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException + */ + public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints) + { + if ($this->_transactionNestingLevel > 0) { + throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction(); + } + + if ( ! $this->getDatabasePlatform()->supportsSavepoints()) { + throw ConnectionException::savepointsNotSupported(); + } + + $this->_nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints; + } + + /** + * Gets if nested transactions should use savepoints. + * + * @return boolean + */ + public function getNestTransactionsWithSavepoints() + { + return $this->_nestTransactionsWithSavepoints; + } + + /** + * Returns the savepoint name to use for nested transactions are false if they are not supported + * "savepointFormat" parameter is not set + * + * @return mixed A string with the savepoint name or false. + */ + protected function _getNestedTransactionSavePointName() + { + return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel; + } + + /** + * Starts a transaction by suspending auto-commit mode. + * + * @return void + */ + public function beginTransaction() + { + $this->connect(); + + ++$this->_transactionNestingLevel; + + $logger = $this->_config->getSQLLogger(); + + if ($this->_transactionNestingLevel == 1) { + if ($logger) { + $logger->startQuery('"START TRANSACTION"'); + } + $this->_conn->beginTransaction(); + if ($logger) { + $logger->stopQuery(); + } + } elseif ($this->_nestTransactionsWithSavepoints) { + if ($logger) { + $logger->startQuery('"SAVEPOINT"'); + } + $this->createSavepoint($this->_getNestedTransactionSavePointName()); + if ($logger) { + $logger->stopQuery(); + } + } + } + + /** + * Commits the current transaction. + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException If the commit failed due to no active transaction or + * because the transaction was marked for rollback only. + */ + public function commit() + { + if ($this->_transactionNestingLevel == 0) { + throw ConnectionException::noActiveTransaction(); + } + if ($this->_isRollbackOnly) { + throw ConnectionException::commitFailedRollbackOnly(); + } + + $this->connect(); + + $logger = $this->_config->getSQLLogger(); + + if ($this->_transactionNestingLevel == 1) { + if ($logger) { + $logger->startQuery('"COMMIT"'); + } + $this->_conn->commit(); + if ($logger) { + $logger->stopQuery(); + } + } elseif ($this->_nestTransactionsWithSavepoints) { + if ($logger) { + $logger->startQuery('"RELEASE SAVEPOINT"'); + } + $this->releaseSavepoint($this->_getNestedTransactionSavePointName()); + if ($logger) { + $logger->stopQuery(); + } + } + + --$this->_transactionNestingLevel; + + if (false === $this->autoCommit && 0 === $this->_transactionNestingLevel) { + $this->beginTransaction(); + } + } + + /** + * Commits all current nesting transactions. + */ + private function commitAll() + { + while (0 !== $this->_transactionNestingLevel) { + if (false === $this->autoCommit && 1 === $this->_transactionNestingLevel) { + // When in no auto-commit mode, the last nesting commit immediately starts a new transaction. + // Therefore we need to do the final commit here and then leave to avoid an infinite loop. + $this->commit(); + + return; + } + + $this->commit(); + } + } + + /** + * Cancels any database changes done during the current transaction. + * + * This method can be listened with onPreTransactionRollback and onTransactionRollback + * eventlistener methods. + * + * @throws \Doctrine\DBAL\ConnectionException If the rollback operation failed. + */ + public function rollBack() + { + if ($this->_transactionNestingLevel == 0) { + throw ConnectionException::noActiveTransaction(); + } + + $this->connect(); + + $logger = $this->_config->getSQLLogger(); + + if ($this->_transactionNestingLevel == 1) { + if ($logger) { + $logger->startQuery('"ROLLBACK"'); + } + $this->_transactionNestingLevel = 0; + $this->_conn->rollback(); + $this->_isRollbackOnly = false; + if ($logger) { + $logger->stopQuery(); + } + + if (false === $this->autoCommit) { + $this->beginTransaction(); + } + } elseif ($this->_nestTransactionsWithSavepoints) { + if ($logger) { + $logger->startQuery('"ROLLBACK TO SAVEPOINT"'); + } + $this->rollbackSavepoint($this->_getNestedTransactionSavePointName()); + --$this->_transactionNestingLevel; + if ($logger) { + $logger->stopQuery(); + } + } else { + $this->_isRollbackOnly = true; + --$this->_transactionNestingLevel; + } + } + + /** + * Creates a new savepoint. + * + * @param string $savepoint The name of the savepoint to create. + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException + */ + public function createSavepoint($savepoint) + { + if ( ! $this->getDatabasePlatform()->supportsSavepoints()) { + throw ConnectionException::savepointsNotSupported(); + } + + $this->_conn->exec($this->platform->createSavePoint($savepoint)); + } + + /** + * Releases the given savepoint. + * + * @param string $savepoint The name of the savepoint to release. + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException + */ + public function releaseSavepoint($savepoint) + { + if ( ! $this->getDatabasePlatform()->supportsSavepoints()) { + throw ConnectionException::savepointsNotSupported(); + } + + if ($this->platform->supportsReleaseSavepoints()) { + $this->_conn->exec($this->platform->releaseSavePoint($savepoint)); + } + } + + /** + * Rolls back to the given savepoint. + * + * @param string $savepoint The name of the savepoint to rollback to. + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException + */ + public function rollbackSavepoint($savepoint) + { + if ( ! $this->getDatabasePlatform()->supportsSavepoints()) { + throw ConnectionException::savepointsNotSupported(); + } + + $this->_conn->exec($this->platform->rollbackSavePoint($savepoint)); + } + + /** + * Gets the wrapped driver connection. + * + * @return \Doctrine\DBAL\Driver\Connection + */ + public function getWrappedConnection() + { + $this->connect(); + + return $this->_conn; + } + + /** + * Gets the SchemaManager that can be used to inspect or change the + * database schema through the connection. + * + * @return \Doctrine\DBAL\Schema\AbstractSchemaManager + */ + public function getSchemaManager() + { + if ( ! $this->_schemaManager) { + $this->_schemaManager = $this->_driver->getSchemaManager($this); + } + + return $this->_schemaManager; + } + + /** + * Marks the current transaction so that the only possible + * outcome for the transaction to be rolled back. + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException If no transaction is active. + */ + public function setRollbackOnly() + { + if ($this->_transactionNestingLevel == 0) { + throw ConnectionException::noActiveTransaction(); + } + $this->_isRollbackOnly = true; + } + + /** + * Checks whether the current transaction is marked for rollback only. + * + * @return boolean + * + * @throws \Doctrine\DBAL\ConnectionException If no transaction is active. + */ + public function isRollbackOnly() + { + if ($this->_transactionNestingLevel == 0) { + throw ConnectionException::noActiveTransaction(); + } + + return $this->_isRollbackOnly; + } + + /** + * Converts a given value to its database representation according to the conversion + * rules of a specific DBAL mapping type. + * + * @param mixed $value The value to convert. + * @param string $type The name of the DBAL mapping type. + * + * @return mixed The converted value. + */ + public function convertToDatabaseValue($value, $type) + { + return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform()); + } + + /** + * Converts a given value to its PHP representation according to the conversion + * rules of a specific DBAL mapping type. + * + * @param mixed $value The value to convert. + * @param string $type The name of the DBAL mapping type. + * + * @return mixed The converted type. + */ + public function convertToPHPValue($value, $type) + { + return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform()); + } + + /** + * Binds a set of parameters, some or all of which are typed with a PDO binding type + * or DBAL mapping type, to a given statement. + * + * @param \Doctrine\DBAL\Driver\Statement $stmt The statement to bind the values to. + * @param array $params The map/list of named/positional parameters. + * @param array $types The parameter types (PDO binding types or DBAL mapping types). + * + * @return void + * + * @internal Duck-typing used on the $stmt parameter to support driver statements as well as + * raw PDOStatement instances. + */ + private function _bindTypedValues($stmt, array $params, array $types) + { + // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO. + if (is_int(key($params))) { + // Positional parameters + $typeOffset = array_key_exists(0, $types) ? -1 : 0; + $bindIndex = 1; + foreach ($params as $value) { + $typeIndex = $bindIndex + $typeOffset; + if (isset($types[$typeIndex])) { + $type = $types[$typeIndex]; + list($value, $bindingType) = $this->getBindingInfo($value, $type); + $stmt->bindValue($bindIndex, $value, $bindingType); + } else { + $stmt->bindValue($bindIndex, $value); + } + ++$bindIndex; + } + } else { + // Named parameters + foreach ($params as $name => $value) { + if (isset($types[$name])) { + $type = $types[$name]; + list($value, $bindingType) = $this->getBindingInfo($value, $type); + $stmt->bindValue($name, $value, $bindingType); + } else { + $stmt->bindValue($name, $value); + } + } + } + } + + /** + * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type. + * + * @param mixed $value The value to bind. + * @param mixed $type The type to bind (PDO or DBAL). + * + * @return array [0] => the (escaped) value, [1] => the binding type. + */ + private function getBindingInfo($value, $type) + { + if (is_string($type)) { + $type = Type::getType($type); + } + if ($type instanceof Type) { + $value = $type->convertToDatabaseValue($value, $this->getDatabasePlatform()); + $bindingType = $type->getBindingType(); + } else { + $bindingType = $type; // PDO::PARAM_* constants + } + + return array($value, $bindingType); + } + + /** + * Resolves the parameters to a format which can be displayed. + * + * @internal This is a purely internal method. If you rely on this method, you are advised to + * copy/paste the code as this method may change, or be removed without prior notice. + * + * @param array $params + * @param array $types + * + * @return array + */ + public function resolveParams(array $params, array $types) + { + $resolvedParams = array(); + + // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO. + if (is_int(key($params))) { + // Positional parameters + $typeOffset = array_key_exists(0, $types) ? -1 : 0; + $bindIndex = 1; + foreach ($params as $value) { + $typeIndex = $bindIndex + $typeOffset; + if (isset($types[$typeIndex])) { + $type = $types[$typeIndex]; + list($value,) = $this->getBindingInfo($value, $type); + $resolvedParams[$bindIndex] = $value; + } else { + $resolvedParams[$bindIndex] = $value; + } + ++$bindIndex; + } + } else { + // Named parameters + foreach ($params as $name => $value) { + if (isset($types[$name])) { + $type = $types[$name]; + list($value,) = $this->getBindingInfo($value, $type); + $resolvedParams[$name] = $value; + } else { + $resolvedParams[$name] = $value; + } + } + } + + return $resolvedParams; + } + + /** + * Creates a new instance of a SQL query builder. + * + * @return \Doctrine\DBAL\Query\QueryBuilder + */ + public function createQueryBuilder() + { + return new Query\QueryBuilder($this); + } + + /** + * Ping the server + * + * When the server is not available the method returns FALSE. + * It is responsibility of the developer to handle this case + * and abort the request or reconnect manually: + * + * @example + * + * if ($conn->ping() === false) { + * $conn->close(); + * $conn->connect(); + * } + * + * It is undefined if the underlying driver attempts to reconnect + * or disconnect when the connection is not available anymore + * as long it returns TRUE when a reconnect succeeded and + * FALSE when the connection was dropped. + * + * @return bool + */ + public function ping() + { + $this->connect(); + + if ($this->_conn instanceof PingableConnection) { + return $this->_conn->ping(); + } + + try { + $this->query($this->getDatabasePlatform()->getDummySelectSQL()); + + return true; + } catch (DBALException $e) { + return false; + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/ConnectionException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/ConnectionException.php new file mode 100644 index 00000000..86494b03 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/ConnectionException.php @@ -0,0 +1,60 @@ +. + */ + +namespace Doctrine\DBAL; + +/** + * @link www.doctrine-project.org + * @since 2.0 + * @author Jonathan H. Wage . + */ + +namespace Doctrine\DBAL\Connections; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Configuration; +use Doctrine\Common\EventManager; +use Doctrine\DBAL\Event\ConnectionEventArgs; +use Doctrine\DBAL\Events; + +/** + * Master-Slave Connection + * + * Connection can be used with master-slave setups. + * + * Important for the understanding of this connection should be how and when + * it picks the slave or master. + * + * 1. Slave if master was never picked before and ONLY if 'getWrappedConnection' + * or 'executeQuery' is used. + * 2. Master picked when 'exec', 'executeUpdate', 'insert', 'delete', 'update', 'createSavepoint', + * 'releaseSavepoint', 'beginTransaction', 'rollback', 'commit', 'query' or + * 'prepare' is called. + * 3. If master was picked once during the lifetime of the connection it will always get picked afterwards. + * 4. One slave connection is randomly picked ONCE during a request. + * + * ATTENTION: You can write to the slave with this connection if you execute a write query without + * opening up a transaction. For example: + * + * $conn = DriverManager::getConnection(...); + * $conn->executeQuery("DELETE FROM table"); + * + * Be aware that Connection#executeQuery is a method specifically for READ + * operations only. + * + * This connection is limited to slave operations using the + * Connection#executeQuery operation only, because it wouldn't be compatible + * with the ORM or SchemaManager code otherwise. Both use all the other + * operations in a context where writes could happen to a slave, which makes + * this restricted approach necessary. + * + * You can manually connect to the master at any time by calling: + * + * $conn->connect('master'); + * + * Instantiation through the DriverManager looks like: + * + * @example + * + * $conn = DriverManager::getConnection(array( + * 'wrapperClass' => 'Doctrine\DBAL\Connections\MasterSlaveConnection', + * 'driver' => 'pdo_mysql', + * 'master' => array('user' => '', 'password' => '', 'host' => '', 'dbname' => ''), + * 'slaves' => array( + * array('user' => 'slave1', 'password', 'host' => '', 'dbname' => ''), + * array('user' => 'slave2', 'password', 'host' => '', 'dbname' => ''), + * ) + * )); + * + * You can also pass 'driverOptions' and any other documented option to each of this drivers to pass additional information. + * + * @author Lars Strojny + * @author Benjamin Eberlei + */ +class MasterSlaveConnection extends Connection +{ + /** + * Master and slave connection (one of the randomly picked slaves). + * + * @var \Doctrine\DBAL\Driver\Connection[] + */ + protected $connections = array('master' => null, 'slave' => null); + + /** + * You can keep the slave connection and then switch back to it + * during the request if you know what you are doing. + * + * @var boolean + */ + protected $keepSlave = false; + + /** + * Creates Master Slave Connection. + * + * @param array $params + * @param \Doctrine\DBAL\Driver $driver + * @param \Doctrine\DBAL\Configuration|null $config + * @param \Doctrine\Common\EventManager|null $eventManager + * + * @throws \InvalidArgumentException + */ + public function __construct(array $params, Driver $driver, Configuration $config = null, EventManager $eventManager = null) + { + if ( !isset($params['slaves']) || !isset($params['master'])) { + throw new \InvalidArgumentException('master or slaves configuration missing'); + } + if (count($params['slaves']) == 0) { + throw new \InvalidArgumentException('You have to configure at least one slaves.'); + } + + $params['master']['driver'] = $params['driver']; + foreach ($params['slaves'] as $slaveKey => $slave) { + $params['slaves'][$slaveKey]['driver'] = $params['driver']; + } + + $this->keepSlave = isset($params['keepSlave']) ? (bool) $params['keepSlave'] : false; + + parent::__construct($params, $driver, $config, $eventManager); + } + + /** + * Checks if the connection is currently towards the master or not. + * + * @return boolean + */ + public function isConnectedToMaster() + { + return $this->_conn !== null && $this->_conn === $this->connections['master']; + } + + /** + * {@inheritDoc} + */ + public function connect($connectionName = null) + { + $requestedConnectionChange = ($connectionName !== null); + $connectionName = $connectionName ?: 'slave'; + + if ($connectionName !== 'slave' && $connectionName !== 'master') { + throw new \InvalidArgumentException("Invalid option to connect(), only master or slave allowed."); + } + + // If we have a connection open, and this is not an explicit connection + // change request, then abort right here, because we are already done. + // This prevents writes to the slave in case of "keepSlave" option enabled. + if ($this->_conn && !$requestedConnectionChange) { + return false; + } + + $forceMasterAsSlave = false; + + if ($this->getTransactionNestingLevel() > 0) { + $connectionName = 'master'; + $forceMasterAsSlave = true; + } + + if ($this->connections[$connectionName]) { + $this->_conn = $this->connections[$connectionName]; + + if ($forceMasterAsSlave && ! $this->keepSlave) { + $this->connections['slave'] = $this->_conn; + } + + return false; + } + + if ($connectionName === 'master') { + // Set slave connection to master to avoid invalid reads + if ($this->connections['slave'] && ! $this->keepSlave) { + unset($this->connections['slave']); + } + + $this->connections['master'] = $this->_conn = $this->connectTo($connectionName); + + if ( ! $this->keepSlave) { + $this->connections['slave'] = $this->connections['master']; + } + } else { + $this->connections['slave'] = $this->_conn = $this->connectTo($connectionName); + } + + if ($this->_eventManager->hasListeners(Events::postConnect)) { + $eventArgs = new ConnectionEventArgs($this); + $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); + } + + return true; + } + + /** + * Connects to a specific connection. + * + * @param string $connectionName + * + * @return \Doctrine\DBAL\Driver + */ + protected function connectTo($connectionName) + { + $params = $this->getParams(); + + $driverOptions = isset($params['driverOptions']) ? $params['driverOptions'] : array(); + + $connectionParams = $this->chooseConnectionConfiguration($connectionName, $params); + + $user = isset($connectionParams['user']) ? $connectionParams['user'] : null; + $password = isset($connectionParams['password']) ? $connectionParams['password'] : null; + + return $this->_driver->connect($connectionParams, $user, $password, $driverOptions); + } + + /** + * @param string $connectionName + * @param array $params + * + * @return mixed + */ + protected function chooseConnectionConfiguration($connectionName, $params) + { + if ($connectionName === 'master') { + return $params['master']; + } + + return $params['slaves'][array_rand($params['slaves'])]; + } + + /** + * {@inheritDoc} + */ + public function executeUpdate($query, array $params = array(), array $types = array()) + { + $this->connect('master'); + + return parent::executeUpdate($query, $params, $types); + } + + /** + * {@inheritDoc} + */ + public function beginTransaction() + { + $this->connect('master'); + + parent::beginTransaction(); + } + + /** + * {@inheritDoc} + */ + public function commit() + { + $this->connect('master'); + + parent::commit(); + } + + /** + * {@inheritDoc} + */ + public function rollBack() + { + $this->connect('master'); + + return parent::rollBack(); + } + + /** + * {@inheritDoc} + */ + public function delete($tableName, array $identifier, array $types = array()) + { + $this->connect('master'); + + return parent::delete($tableName, $identifier, $types); + } + + /** + * {@inheritDoc} + */ + public function close() + { + unset($this->connections['master']); + unset($this->connections['slave']); + + parent::close(); + + $this->_conn = null; + $this->connections = array('master' => null, 'slave' => null); + } + + /** + * {@inheritDoc} + */ + public function update($tableName, array $data, array $identifier, array $types = array()) + { + $this->connect('master'); + + return parent::update($tableName, $data, $identifier, $types); + } + + /** + * {@inheritDoc} + */ + public function insert($tableName, array $data, array $types = array()) + { + $this->connect('master'); + + return parent::insert($tableName, $data, $types); + } + + /** + * {@inheritDoc} + */ + public function exec($statement) + { + $this->connect('master'); + + return parent::exec($statement); + } + + /** + * {@inheritDoc} + */ + public function createSavepoint($savepoint) + { + $this->connect('master'); + + parent::createSavepoint($savepoint); + } + + /** + * {@inheritDoc} + */ + public function releaseSavepoint($savepoint) + { + $this->connect('master'); + + parent::releaseSavepoint($savepoint); + } + + /** + * {@inheritDoc} + */ + public function rollbackSavepoint($savepoint) + { + $this->connect('master'); + + parent::rollbackSavepoint($savepoint); + } + + /** + * {@inheritDoc} + */ + public function query() + { + $this->connect('master'); + + $args = func_get_args(); + + $logger = $this->getConfiguration()->getSQLLogger(); + if ($logger) { + $logger->startQuery($args[0]); + } + + $statement = call_user_func_array(array($this->_conn, 'query'), $args); + + if ($logger) { + $logger->stopQuery(); + } + + return $statement; + } + + /** + * {@inheritDoc} + */ + public function prepare($statement) + { + $this->connect('master'); + + return parent::prepare($statement); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/DBALException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/DBALException.php new file mode 100644 index 00000000..b0eac0b3 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/DBALException.php @@ -0,0 +1,259 @@ +. + */ + +namespace Doctrine\DBAL; + +use Doctrine\DBAL\Driver\DriverException; +use Doctrine\DBAL\Driver\ExceptionConverterDriver; + +class DBALException extends \Exception +{ + /** + * @param string $method + * + * @return \Doctrine\DBAL\DBALException + */ + public static function notSupported($method) + { + return new self("Operation '$method' is not supported by platform."); + } + + /** + * @return \Doctrine\DBAL\DBALException + */ + public static function invalidPlatformSpecified() + { + return new self( + "Invalid 'platform' option specified, need to give an instance of ". + "\Doctrine\DBAL\Platforms\AbstractPlatform."); + } + + /** + * Returns a new instance for an invalid specified platform version. + * + * @param string $version The invalid platform version given. + * @param string $expectedFormat The expected platform version format. + * + * @return DBALException + */ + public static function invalidPlatformVersionSpecified($version, $expectedFormat) + { + return new self( + sprintf( + 'Invalid platform version "%s" specified. ' . + 'The platform version has to be specified in the format: "%s".', + $version, + $expectedFormat + ) + ); + } + + /** + * @return \Doctrine\DBAL\DBALException + */ + public static function invalidPdoInstance() + { + return new self( + "The 'pdo' option was used in DriverManager::getConnection() but no ". + "instance of PDO was given." + ); + } + + /** + * @param string|null $url The URL that was provided in the connection parameters (if any). + * + * @return \Doctrine\DBAL\DBALException + */ + public static function driverRequired($url = null) + { + if ($url) { + return new self( + sprintf( + "The options 'driver' or 'driverClass' are mandatory if a connection URL without scheme " . + "is given to DriverManager::getConnection(). Given URL: %s", + $url + ) + ); + } + + return new self("The options 'driver' or 'driverClass' are mandatory if no PDO ". + "instance is given to DriverManager::getConnection()."); + } + + /** + * @param string $unknownDriverName + * @param array $knownDrivers + * + * @return \Doctrine\DBAL\DBALException + */ + public static function unknownDriver($unknownDriverName, array $knownDrivers) + { + return new self("The given 'driver' ".$unknownDriverName." is unknown, ". + "Doctrine currently supports only the following drivers: ".implode(", ", $knownDrivers)); + } + + /** + * @param \Doctrine\DBAL\Driver $driver + * @param \Exception $driverEx + * @param string $sql + * @param array $params + * + * @return \Doctrine\DBAL\DBALException + */ + public static function driverExceptionDuringQuery(Driver $driver, \Exception $driverEx, $sql, array $params = array()) + { + $msg = "An exception occurred while executing '".$sql."'"; + if ($params) { + $msg .= " with params " . self::formatParameters($params); + } + $msg .= ":\n\n".$driverEx->getMessage(); + + if ($driver instanceof ExceptionConverterDriver && $driverEx instanceof DriverException) { + return $driver->convertException($msg, $driverEx); + } + + return new self($msg, 0, $driverEx); + } + + /** + * @param \Doctrine\DBAL\Driver $driver + * @param \Exception $driverEx + * + * @return \Doctrine\DBAL\DBALException + */ + public static function driverException(Driver $driver, \Exception $driverEx) + { + $msg = "An exception occured in driver: " . $driverEx->getMessage(); + + if ($driver instanceof ExceptionConverterDriver && $driverEx instanceof DriverException) { + return $driver->convertException($msg, $driverEx); + } + + return new self($msg, 0, $driverEx); + } + + /** + * Returns a human-readable representation of an array of parameters. + * This properly handles binary data by returning a hex representation. + * + * @param array $params + * + * @return string + */ + private static function formatParameters(array $params) + { + return '[' . implode(', ', array_map(function ($param) { + $json = @json_encode($param); + + if (! is_string($json) || $json == 'null' && is_string($param)) { + // JSON encoding failed, this is not a UTF-8 string. + return '"\x' . implode('\x', str_split(bin2hex($param), 2)) . '"'; + } + + return $json; + }, $params)) . ']'; + } + + /** + * @param string $wrapperClass + * + * @return \Doctrine\DBAL\DBALException + */ + public static function invalidWrapperClass($wrapperClass) + { + return new self("The given 'wrapperClass' ".$wrapperClass." has to be a ". + "subtype of \Doctrine\DBAL\Connection."); + } + + /** + * @param string $driverClass + * + * @return \Doctrine\DBAL\DBALException + */ + public static function invalidDriverClass($driverClass) + { + return new self("The given 'driverClass' ".$driverClass." has to implement the ". + "\Doctrine\DBAL\Driver interface."); + } + + /** + * @param string $tableName + * + * @return \Doctrine\DBAL\DBALException + */ + public static function invalidTableName($tableName) + { + return new self("Invalid table name specified: ".$tableName); + } + + /** + * @param string $tableName + * + * @return \Doctrine\DBAL\DBALException + */ + public static function noColumnsSpecifiedForTable($tableName) + { + return new self("No columns specified for table ".$tableName); + } + + /** + * @return \Doctrine\DBAL\DBALException + */ + public static function limitOffsetInvalid() + { + return new self("Invalid Offset in Limit Query, it has to be larger or equal to 0."); + } + + /** + * @param string $name + * + * @return \Doctrine\DBAL\DBALException + */ + public static function typeExists($name) + { + return new self('Type '.$name.' already exists.'); + } + + /** + * @param string $name + * + * @return \Doctrine\DBAL\DBALException + */ + public static function unknownColumnType($name) + { + return new self('Unknown column type "'.$name.'" requested. Any Doctrine type that you use has ' . + 'to be registered with \Doctrine\DBAL\Types\Type::addType(). You can get a list of all the ' . + 'known types with \Doctrine\DBAL\Types\Type::getTypesMap(). If this error occurs during database ' . + 'introspection then you might have forgot to register all database types for a Doctrine Type. Use ' . + 'AbstractPlatform#registerDoctrineTypeMapping() or have your custom types implement ' . + 'Type#getMappedDatabaseTypes(). If the type name is empty you might ' . + 'have a problem with the cache or forgot some mapping information.' + ); + } + + /** + * @param string $name + * + * @return \Doctrine\DBAL\DBALException + */ + public static function typeNotFound($name) + { + return new self('Type to be overwritten '.$name.' does not exist.'); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver.php new file mode 100644 index 00000000..21d60646 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver.php @@ -0,0 +1,75 @@ +. + */ + +namespace Doctrine\DBAL; + +/** + * Driver interface. + * Interface that all DBAL drivers must implement. + * + * @since 2.0 + */ +interface Driver +{ + /** + * Attempts to create a connection with the database. + * + * @param array $params All connection parameters passed by the user. + * @param string|null $username The username to use when connecting. + * @param string|null $password The password to use when connecting. + * @param array $driverOptions The driver options to use when connecting. + * + * @return \Doctrine\DBAL\Driver\Connection The database connection. + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()); + + /** + * Gets the DatabasePlatform instance that provides all the metadata about + * the platform this driver connects to. + * + * @return \Doctrine\DBAL\Platforms\AbstractPlatform The database platform. + */ + public function getDatabasePlatform(); + + /** + * Gets the SchemaManager that can be used to inspect and change the underlying + * database schema of the platform this driver connects to. + * + * @param \Doctrine\DBAL\Connection $conn + * + * @return \Doctrine\DBAL\Schema\AbstractSchemaManager + */ + public function getSchemaManager(Connection $conn); + + /** + * Gets the name of the driver. + * + * @return string The name of the driver. + */ + public function getName(); + + /** + * Gets the name of the database connected to for this driver. + * + * @param \Doctrine\DBAL\Connection $conn + * + * @return string The name of the database. + */ + public function getDatabase(Connection $conn); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractDB2Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractDB2Driver.php new file mode 100644 index 00000000..baa08169 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractDB2Driver.php @@ -0,0 +1,60 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Platforms\DB2Platform; +use Doctrine\DBAL\Schema\DB2SchemaManager; + +/** + * Abstract base implementation of the {@link Doctrine\DBAL\Driver} interface for IBM DB2 based drivers. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +abstract class AbstractDB2Driver implements Driver +{ + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + return $params['dbname']; + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new DB2Platform(); + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new DB2SchemaManager($conn); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractDriverException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractDriverException.php new file mode 100644 index 00000000..c5eee5a0 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractDriverException.php @@ -0,0 +1,75 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +/** + * Abstract base implementation of the {@link DriverException} interface. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +abstract class AbstractDriverException extends \Exception implements DriverException +{ + /** + * The driver specific error code. + * + * @var integer|string|null + */ + private $errorCode; + + /** + * The SQLSTATE of the driver. + * + * @var string|null + */ + private $sqlState; + + /** + * Constructor. + * + * @param string $message The driver error message. + * @param string|null $sqlState The SQLSTATE the driver is in at the time the error occured, if any. + * @param integer|string|null $errorCode The driver specific error code if any. + */ + public function __construct($message, $sqlState = null, $errorCode = null) + { + parent::__construct($message); + + $this->errorCode = $errorCode; + $this->sqlState = $sqlState; + } + + /** + * {@inheritdoc} + */ + public function getErrorCode() + { + return $this->errorCode; + } + + /** + * {@inheritdoc} + */ + public function getSQLState() + { + return $this->sqlState; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php new file mode 100644 index 00000000..eff79576 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php @@ -0,0 +1,175 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Platforms\MySQL57Platform; +use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Schema\MySqlSchemaManager; +use Doctrine\DBAL\VersionAwarePlatformDriver; + +/** + * Abstract base implementation of the {@link Doctrine\DBAL\Driver} interface for MySQL based drivers. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +abstract class AbstractMySQLDriver implements Driver, ExceptionConverterDriver, VersionAwarePlatformDriver +{ + /** + * {@inheritdoc} + * + * @link http://dev.mysql.com/doc/refman/5.7/en/error-messages-client.html + * @link http://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html + */ + public function convertException($message, DriverException $exception) + { + switch ($exception->getErrorCode()) { + case '1050': + return new Exception\TableExistsException($message, $exception); + + case '1051': + case '1146': + return new Exception\TableNotFoundException($message, $exception); + + case '1216': + case '1217': + case '1451': + case '1452': + case '1701': + return new Exception\ForeignKeyConstraintViolationException($message, $exception); + + case '1062': + case '1557': + case '1569': + case '1586': + return new Exception\UniqueConstraintViolationException($message, $exception); + + case '1054': + case '1166': + case '1611': + return new Exception\InvalidFieldNameException($message, $exception); + + case '1052': + case '1060': + case '1110': + return new Exception\NonUniqueFieldNameException($message, $exception); + + case '1064': + case '1149': + case '1287': + case '1341': + case '1342': + case '1343': + case '1344': + case '1382': + case '1479': + case '1541': + case '1554': + case '1626': + return new Exception\SyntaxErrorException($message, $exception); + + case '1044': + case '1045': + case '1046': + case '1049': + case '1095': + case '1142': + case '1143': + case '1227': + case '1370': + case '2002': + case '2005': + return new Exception\ConnectionException($message, $exception); + + case '1048': + case '1121': + case '1138': + case '1171': + case '1252': + case '1263': + case '1566': + return new Exception\NotNullConstraintViolationException($message, $exception); + } + + return new Exception\DriverException($message, $exception); + } + + /** + * {@inheritdoc} + */ + public function createDatabasePlatformForVersion($version) + { + if ( ! preg_match('/^(?P\d+)(?:\.(?P\d+)(?:\.(?P\d+))?)?/', $version, $versionParts)) { + throw DBALException::invalidPlatformVersionSpecified( + $version, + '..' + ); + } + + if (false !== stripos($version, 'mariadb')) { + return $this->getDatabasePlatform(); + } + + $majorVersion = $versionParts['major']; + $minorVersion = isset($versionParts['minor']) ? $versionParts['minor'] : 0; + $patchVersion = isset($versionParts['patch']) ? $versionParts['patch'] : 0; + $version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion; + + if (version_compare($version, '5.7', '>=')) { + return new MySQL57Platform(); + } + + return $this->getDatabasePlatform(); + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + if (isset($params['dbname'])) { + return $params['dbname']; + } + + return $conn->query('SELECT DATABASE()')->fetchColumn(); + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new MySqlPlatform(); + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new MySqlSchemaManager($conn); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractOracleDriver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractOracleDriver.php new file mode 100644 index 00000000..1a0d9777 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractOracleDriver.php @@ -0,0 +1,151 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Platforms\OraclePlatform; +use Doctrine\DBAL\Schema\OracleSchemaManager; + +/** + * Abstract base implementation of the {@link Doctrine\DBAL\Driver} interface for Oracle based drivers. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +abstract class AbstractOracleDriver implements Driver, ExceptionConverterDriver +{ + /** + * {@inheritdoc} + */ + public function convertException($message, DriverException $exception) + { + switch ($exception->getErrorCode()) { + case '1': + case '2299': + case '38911': + return new Exception\UniqueConstraintViolationException($message, $exception); + + case '904': + return new Exception\InvalidFieldNameException($message, $exception); + + case '918': + case '960': + return new Exception\NonUniqueFieldNameException($message, $exception); + + case '923': + return new Exception\SyntaxErrorException($message, $exception); + + case '942': + return new Exception\TableNotFoundException($message, $exception); + + case '955': + return new Exception\TableExistsException($message, $exception); + + case '1017': + case '12545': + return new Exception\ConnectionException($message, $exception); + + case '1400': + return new Exception\NotNullConstraintViolationException($message, $exception); + + case '2266': + case '2291': + case '2292': + return new Exception\ForeignKeyConstraintViolationException($message, $exception); + } + + return new Exception\DriverException($message, $exception); + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + return $params['user']; + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new OraclePlatform(); + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new OracleSchemaManager($conn); + } + + /** + * Returns an appropriate Easy Connect String for the given parameters. + * + * @param array $params The connection parameters to return the Easy Connect STring for. + * + * @return string + * + * @link http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm + */ + protected function getEasyConnectString(array $params) + { + if ( ! empty($params['host'])) { + if ( ! isset($params['port'])) { + $params['port'] = 1521; + } + + $serviceName = $params['dbname']; + + if ( ! empty($params['servicename'])) { + $serviceName = $params['servicename']; + } + + $service = 'SID=' . $serviceName; + $pooled = ''; + $instance = ''; + + if (isset($params['service']) && $params['service'] == true) { + $service = 'SERVICE_NAME=' . $serviceName; + } + + if (isset($params['instancename']) && ! empty($params['instancename'])) { + $instance = '(INSTANCE_NAME = ' . $params['instancename'] . ')'; + } + + if (isset($params['pooled']) && $params['pooled'] == true) { + $pooled = '(SERVER=POOLED)'; + } + + return '(DESCRIPTION=' . + '(ADDRESS=(PROTOCOL=TCP)(HOST=' . $params['host'] . ')(PORT=' . $params['port'] . '))' . + '(CONNECT_DATA=(' . $service . ')' . $instance . $pooled . '))'; + + } + + return isset($params['dbname']) ? $params['dbname'] : ''; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractPostgreSQLDriver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractPostgreSQLDriver.php new file mode 100644 index 00000000..4ad7cbee --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractPostgreSQLDriver.php @@ -0,0 +1,148 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Platforms\PostgreSQL91Platform; +use Doctrine\DBAL\Platforms\PostgreSQL92Platform; +use Doctrine\DBAL\Platforms\PostgreSqlPlatform; +use Doctrine\DBAL\Schema\PostgreSqlSchemaManager; +use Doctrine\DBAL\VersionAwarePlatformDriver; + +/** + * Abstract base implementation of the {@link Doctrine\DBAL\Driver} interface for PostgreSQL based drivers. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +abstract class AbstractPostgreSQLDriver implements Driver, ExceptionConverterDriver, VersionAwarePlatformDriver +{ + /** + * {@inheritdoc} + * + * @link http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html + */ + public function convertException($message, DriverException $exception) + { + switch ($exception->getSQLState()) { + case '0A000': + // Foreign key constraint violations during a TRUNCATE operation + // are considered "feature not supported" in PostgreSQL. + if (strpos($exception->getMessage(), 'truncate') !== false) { + return new Exception\ForeignKeyConstraintViolationException($message, $exception); + } + + break; + case '23502': + return new Exception\NotNullConstraintViolationException($message, $exception); + + case '23503': + return new Exception\ForeignKeyConstraintViolationException($message, $exception); + + case '23505': + return new Exception\UniqueConstraintViolationException($message, $exception); + + case '42601': + return new Exception\SyntaxErrorException($message, $exception); + + case '42702': + return new Exception\NonUniqueFieldNameException($message, $exception); + + case '42703': + return new Exception\InvalidFieldNameException($message, $exception); + + case '42P01': + return new Exception\TableNotFoundException($message, $exception); + + case '42P07': + return new Exception\TableExistsException($message, $exception); + + case '7': + // In some case (mainly connection errors) the PDO exception does not provide a SQLSTATE via its code. + // The exception code is always set to 7 here. + // We have to match against the SQLSTATE in the error message in these cases. + if (strpos($exception->getMessage(), 'SQLSTATE[08006]') !== false) { + return new Exception\ConnectionException($message, $exception); + } + + break; + } + + return new Exception\DriverException($message, $exception); + } + + /** + * {@inheritdoc} + */ + public function createDatabasePlatformForVersion($version) + { + if ( ! preg_match('/^(?P\d+)(?:\.(?P\d+)(?:\.(?P\d+))?)?/', $version, $versionParts)) { + throw DBALException::invalidPlatformVersionSpecified( + $version, + '..' + ); + } + + $majorVersion = $versionParts['major']; + $minorVersion = isset($versionParts['minor']) ? $versionParts['minor'] : 0; + $patchVersion = isset($versionParts['patch']) ? $versionParts['patch'] : 0; + $version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion; + + switch(true) { + case version_compare($version, '9.2', '>='): + return new PostgreSQL92Platform(); + case version_compare($version, '9.1', '>='): + return new PostgreSQL91Platform(); + default: + return new PostgreSqlPlatform(); + } + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + return (isset($params['dbname'])) + ? $params['dbname'] + : $conn->query('SELECT CURRENT_DATABASE()')->fetchColumn(); + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new PostgreSqlPlatform(); + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new PostgreSqlSchemaManager($conn); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractSQLAnywhereDriver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractSQLAnywhereDriver.php new file mode 100644 index 00000000..0527555a --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractSQLAnywhereDriver.php @@ -0,0 +1,141 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Platforms\SQLAnywhere11Platform; +use Doctrine\DBAL\Platforms\SQLAnywhere12Platform; +use Doctrine\DBAL\Platforms\SQLAnywhere16Platform; +use Doctrine\DBAL\Platforms\SQLAnywherePlatform; +use Doctrine\DBAL\Schema\SQLAnywhereSchemaManager; +use Doctrine\DBAL\VersionAwarePlatformDriver; + +/** + * Abstract base implementation of the {@link Doctrine\DBAL\Driver} interface for SAP Sybase SQL Anywhere based drivers. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +abstract class AbstractSQLAnywhereDriver implements Driver, ExceptionConverterDriver, VersionAwarePlatformDriver +{ + /** + * {@inheritdoc} + * + * @link http://dcx.sybase.com/index.html#sa160/en/saerrors/sqlerror.html + */ + public function convertException($message, DriverException $exception) + { + switch ($exception->getErrorCode()) { + case '-100': + case '-103': + case '-832': + return new Exception\ConnectionException($message, $exception); + case '-143': + return new Exception\InvalidFieldNameException($message, $exception); + case '-193': + case '-196': + return new Exception\UniqueConstraintViolationException($message, $exception); + case '-194': + case '-198': + return new Exception\ForeignKeyConstraintViolationException($message, $exception); + case '-144': + return new Exception\NonUniqueFieldNameException($message, $exception); + case '-184': + case '-195': + return new Exception\NotNullConstraintViolationException($message, $exception); + case '-131': + return new Exception\SyntaxErrorException($message, $exception); + case '-110': + return new Exception\TableExistsException($message, $exception); + case '-141': + case '-1041': + return new Exception\TableNotFoundException($message, $exception); + } + + return new Exception\DriverException($message, $exception); + } + + /** + * {@inheritdoc} + */ + public function createDatabasePlatformForVersion($version) + { + if ( ! preg_match( + '/^(?P\d+)(?:\.(?P\d+)(?:\.(?P\d+)(?:\.(?P\d+))?)?)?/', + $version, + $versionParts + )) { + throw DBALException::invalidPlatformVersionSpecified( + $version, + '...' + ); + } + + $majorVersion = $versionParts['major']; + $minorVersion = isset($versionParts['minor']) ? $versionParts['minor'] : 0; + $patchVersion = isset($versionParts['patch']) ? $versionParts['patch'] : 0; + $buildVersion = isset($versionParts['build']) ? $versionParts['build'] : 0; + $version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion . '.' . $buildVersion; + + switch(true) { + case version_compare($version, '16', '>='): + return new SQLAnywhere16Platform(); + case version_compare($version, '12', '>='): + return new SQLAnywhere12Platform(); + case version_compare($version, '11', '>='): + return new SQLAnywhere11Platform(); + default: + return new SQLAnywherePlatform(); + } + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + if (isset($params['dbname'])) { + return $params['dbname']; + } + + return $conn->query('SELECT DB_NAME()')->fetchColumn(); + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new SQLAnywhere12Platform(); + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new SQLAnywhereSchemaManager($conn); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractSQLServerDriver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractSQLServerDriver.php new file mode 100644 index 00000000..53a4dd52 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractSQLServerDriver.php @@ -0,0 +1,104 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Platforms\SQLServer2005Platform; +use Doctrine\DBAL\Platforms\SQLServer2008Platform; +use Doctrine\DBAL\Platforms\SQLServer2012Platform; +use Doctrine\DBAL\Platforms\SQLServerPlatform; +use Doctrine\DBAL\Schema\SQLServerSchemaManager; +use Doctrine\DBAL\VersionAwarePlatformDriver; + +/** + * Abstract base implementation of the {@link Doctrine\DBAL\Driver} interface for Microsoft SQL Server based drivers. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +abstract class AbstractSQLServerDriver implements Driver, VersionAwarePlatformDriver +{ + /** + * {@inheritdoc} + */ + public function createDatabasePlatformForVersion($version) + { + if ( ! preg_match( + '/^(?P\d+)(?:\.(?P\d+)(?:\.(?P\d+)(?:\.(?P\d+))?)?)?/', + $version, + $versionParts + )) { + throw DBALException::invalidPlatformVersionSpecified( + $version, + '...' + ); + } + + $majorVersion = $versionParts['major']; + $minorVersion = isset($versionParts['minor']) ? $versionParts['minor'] : 0; + $patchVersion = isset($versionParts['patch']) ? $versionParts['patch'] : 0; + $buildVersion = isset($versionParts['build']) ? $versionParts['build'] : 0; + $version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion . '.' . $buildVersion; + + switch(true) { + case version_compare($version, '11.00.2100', '>='): + return new SQLServer2012Platform(); + case version_compare($version, '10.00.1600', '>='): + return new SQLServer2008Platform(); + case version_compare($version, '9.00.1399', '>='): + return new SQLServer2005Platform(); + default: + return new SQLServerPlatform(); + } + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + if (isset($params['dbname'])) { + return $params['dbname']; + } + + return $conn->query('SELECT DB_NAME()')->fetchColumn(); + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new SQLServer2008Platform(); + } + + /** + * {@inheritdoc} + */ + + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new SQLServerSchemaManager($conn); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractSQLiteDriver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractSQLiteDriver.php new file mode 100644 index 00000000..0facc9b8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractSQLiteDriver.php @@ -0,0 +1,113 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Schema\SqliteSchemaManager; + +/** + * Abstract base implementation of the {@link Doctrine\DBAL\Driver} interface for SQLite based drivers. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +abstract class AbstractSQLiteDriver implements Driver, ExceptionConverterDriver +{ + /** + * {@inheritdoc} + * + * @link http://www.sqlite.org/c3ref/c_abort.html + */ + public function convertException($message, DriverException $exception) + { + if (strpos($exception->getMessage(), 'must be unique') !== false || + strpos($exception->getMessage(), 'is not unique') !== false || + strpos($exception->getMessage(), 'are not unique') !== false || + strpos($exception->getMessage(), 'UNIQUE constraint failed') !== false + ) { + return new Exception\UniqueConstraintViolationException($message, $exception); + } + + if (strpos($exception->getMessage(), 'may not be NULL') !== false || + strpos($exception->getMessage(), 'NOT NULL constraint failed') !== false + ) { + return new Exception\NotNullConstraintViolationException($message, $exception); + } + + if (strpos($exception->getMessage(), 'no such table:') !== false) { + return new Exception\TableNotFoundException($message, $exception); + } + + if (strpos($exception->getMessage(), 'already exists') !== false) { + return new Exception\TableExistsException($message, $exception); + } + + if (strpos($exception->getMessage(), 'has no column named') !== false) { + return new Exception\InvalidFieldNameException($message, $exception); + } + + if (strpos($exception->getMessage(), 'ambiguous column name') !== false) { + return new Exception\NonUniqueFieldNameException($message, $exception); + } + + if (strpos($exception->getMessage(), 'syntax error') !== false) { + return new Exception\SyntaxErrorException($message, $exception); + } + + if (strpos($exception->getMessage(), 'attempt to write a readonly database') !== false) { + return new Exception\ReadOnlyException($message, $exception); + } + + if (strpos($exception->getMessage(), 'unable to open database file') !== false) { + return new Exception\ConnectionException($message, $exception); + } + + return new Exception\DriverException($message, $exception); + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + return isset($params['path']) ? $params['path'] : null; + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new SqlitePlatform(); + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new SqliteSchemaManager($conn); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Connection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Connection.php new file mode 100644 index 00000000..401f2175 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Connection.php @@ -0,0 +1,110 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +/** + * Connection interface. + * Driver connections must implement this interface. + * + * This resembles (a subset of) the PDO interface. + * + * @since 2.0 + */ +interface Connection +{ + /** + * Prepares a statement for execution and returns a Statement object. + * + * @param string $prepareString + * + * @return \Doctrine\DBAL\Driver\Statement + */ + function prepare($prepareString); + + /** + * Executes an SQL statement, returning a result set as a Statement object. + * + * @return \Doctrine\DBAL\Driver\Statement + */ + function query(); + + /** + * Quotes a string for use in a query. + * + * @param string $input + * @param integer $type + * + * @return string + */ + function quote($input, $type=\PDO::PARAM_STR); + + /** + * Executes an SQL statement and return the number of affected rows. + * + * @param string $statement + * + * @return integer + */ + function exec($statement); + + /** + * Returns the ID of the last inserted row or sequence value. + * + * @param string|null $name + * + * @return string + */ + function lastInsertId($name = null); + + /** + * Initiates a transaction. + * + * @return boolean TRUE on success or FALSE on failure. + */ + function beginTransaction(); + + /** + * Commits a transaction. + * + * @return boolean TRUE on success or FALSE on failure. + */ + function commit(); + + /** + * Rolls back the current transaction, as initiated by beginTransaction(). + * + * @return boolean TRUE on success or FALSE on failure. + */ + function rollBack(); + + /** + * Returns the error code associated with the last operation on the database handle. + * + * @return string|null The error code, or null if no operation has been run on the database handle. + */ + function errorCode(); + + /** + * Returns extended error information associated with the last operation on the database handle. + * + * @return array + */ + function errorInfo(); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DriverException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DriverException.php new file mode 100644 index 00000000..f11146b1 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DriverException.php @@ -0,0 +1,59 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +/** + * Contract for a driver exception. + * + * Driver exceptions provide the SQLSTATE of the driver + * and the driver specific error code at the time the error occurred. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +interface DriverException +{ + /** + * Returns the driver specific error code if available. + * + * Returns null if no driver specific error code is available + * for the error raised by the driver. + * + * @return integer|string|null + */ + public function getErrorCode(); + + /** + * Returns the driver error message. + * + * @return string + */ + public function getMessage(); + + /** + * Returns the SQLSTATE the driver was in at the time the error occurred. + * + * Returns null if the driver does not provide a SQLSTATE for the error occurred. + * + * @return string|null + */ + public function getSQLState(); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Connection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Connection.php new file mode 100644 index 00000000..92ca6601 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Connection.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\DBAL\Driver\DrizzlePDOMySql; + +/** + * @author Kim Hemsø Rasmussen + */ +class Connection extends \Doctrine\DBAL\Driver\PDOConnection +{ + /** + * {@inheritdoc} + */ + public function quote($value, $type = \PDO::PARAM_STR) + { + if (\PDO::PARAM_BOOL === $type) { + if ($value) { + return 'true'; + } else { + return 'false'; + } + } + + return parent::quote($value, $type); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Driver.php new file mode 100644 index 00000000..ff4cf3b8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Driver.php @@ -0,0 +1,78 @@ +. + */ + +namespace Doctrine\DBAL\Driver\DrizzlePDOMySql; + +use Doctrine\DBAL\Platforms\DrizzlePlatform; +use Doctrine\DBAL\Schema\DrizzleSchemaManager; + +/** + * Drizzle driver using PDO MySql. + * + * @author Kim Hemsø Rasmussen + */ +class Driver extends \Doctrine\DBAL\Driver\PDOMySql\Driver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + $conn = new Connection( + $this->constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + + return $conn; + } + + /** + * {@inheritdoc} + */ + public function createDatabasePlatformForVersion($version) + { + return $this->getDatabasePlatform(); + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new DrizzlePlatform(); + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new DrizzleSchemaManager($conn); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'drizzle_pdo_mysql'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/ExceptionConverterDriver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/ExceptionConverterDriver.php new file mode 100644 index 00000000..0bd8d516 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/ExceptionConverterDriver.php @@ -0,0 +1,44 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +/** + * Contract for a driver that is capable of converting DBAL driver exceptions into standardized DBAL driver exceptions. + * + * @author Benjamin Eberlei + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +interface ExceptionConverterDriver +{ + /** + * Converts a given DBAL driver exception into a standardized DBAL driver exception. + * + * It evaluates the vendor specific error code and SQLSTATE and transforms + * it into a unified {@link Doctrine\DBAL\Exception\DriverException} subclass. + * + * @param string $message The DBAL exception message to use. + * @param \Doctrine\DBAL\Driver\DriverException $exception The DBAL driver exception to convert. + * + * @return \Doctrine\DBAL\Exception\DriverException An instance of one of the DriverException subclasses. + */ + public function convertException($message, DriverException $exception); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php new file mode 100644 index 00000000..0e809e2f --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php @@ -0,0 +1,178 @@ +. + */ + +namespace Doctrine\DBAL\Driver\IBMDB2; + +use Doctrine\DBAL\Driver\Connection; +use Doctrine\DBAL\Driver\ServerInfoAwareConnection; + +class DB2Connection implements Connection, ServerInfoAwareConnection +{ + /** + * @var resource + */ + private $_conn = null; + + /** + * @param array $params + * @param string $username + * @param string $password + * @param array $driverOptions + * + * @throws \Doctrine\DBAL\Driver\IBMDB2\DB2Exception + */ + public function __construct(array $params, $username, $password, $driverOptions = array()) + { + $isPersistant = (isset($params['persistent']) && $params['persistent'] == true); + + if ($isPersistant) { + $this->_conn = db2_pconnect($params['dbname'], $username, $password, $driverOptions); + } else { + $this->_conn = db2_connect($params['dbname'], $username, $password, $driverOptions); + } + if ( ! $this->_conn) { + throw new DB2Exception(db2_conn_errormsg()); + } + } + + /** + * {@inheritdoc} + */ + public function getServerVersion() + { + $serverInfo = db2_server_info($this->_conn); + + return $serverInfo->DBMS_VER; + } + + /** + * {@inheritdoc} + */ + public function requiresQueryForServerVersion() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function prepare($sql) + { + $stmt = @db2_prepare($this->_conn, $sql); + if ( ! $stmt) { + throw new DB2Exception(db2_stmt_errormsg()); + } + + return new DB2Statement($stmt); + } + + /** + * {@inheritdoc} + */ + public function query() + { + $args = func_get_args(); + $sql = $args[0]; + $stmt = $this->prepare($sql); + $stmt->execute(); + + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function quote($input, $type=\PDO::PARAM_STR) + { + $input = db2_escape_string($input); + if ($type == \PDO::PARAM_INT) { + return $input; + } else { + return "'".$input."'"; + } + } + + /** + * {@inheritdoc} + */ + public function exec($statement) + { + $stmt = $this->prepare($statement); + $stmt->execute(); + + return $stmt->rowCount(); + } + + /** + * {@inheritdoc} + */ + public function lastInsertId($name = null) + { + return db2_last_insert_id($this->_conn); + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + db2_autocommit($this->_conn, DB2_AUTOCOMMIT_OFF); + } + + /** + * {@inheritdoc} + */ + public function commit() + { + if (!db2_commit($this->_conn)) { + throw new DB2Exception(db2_conn_errormsg($this->_conn)); + } + db2_autocommit($this->_conn, DB2_AUTOCOMMIT_ON); + } + + /** + * {@inheritdoc} + */ + public function rollBack() + { + if (!db2_rollback($this->_conn)) { + throw new DB2Exception(db2_conn_errormsg($this->_conn)); + } + db2_autocommit($this->_conn, DB2_AUTOCOMMIT_ON); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return db2_conn_error($this->_conn); + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return array( + 0 => db2_conn_errormsg($this->_conn), + 1 => $this->errorCode(), + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php new file mode 100644 index 00000000..8ed82e4d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php @@ -0,0 +1,67 @@ +. + */ + +namespace Doctrine\DBAL\Driver\IBMDB2; + +use Doctrine\DBAL\Driver\AbstractDB2Driver; + +/** + * IBM DB2 Driver. + * + * @since 2.0 + * @author Benjamin Eberlei + */ +class DB2Driver extends AbstractDB2Driver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + if ( ! isset($params['protocol'])) { + $params['protocol'] = 'TCPIP'; + } + + if ($params['host'] !== 'localhost' && $params['host'] != '127.0.0.1') { + // if the host isn't localhost, use extended connection params + $params['dbname'] = 'DRIVER={IBM DB2 ODBC DRIVER}' . + ';DATABASE=' . $params['dbname'] . + ';HOSTNAME=' . $params['host'] . + ';PROTOCOL=' . $params['protocol'] . + ';UID=' . $username . + ';PWD=' . $password .';'; + if (isset($params['port'])) { + $params['dbname'] .= 'PORT=' . $params['port']; + } + + $username = null; + $password = null; + } + + return new DB2Connection($params, $username, $password, $driverOptions); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ibm_db2'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php new file mode 100644 index 00000000..72775cbf --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php @@ -0,0 +1,24 @@ +. + */ + +namespace Doctrine\DBAL\Driver\IBMDB2; + +class DB2Exception extends \Exception +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php new file mode 100644 index 00000000..892db0ae --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php @@ -0,0 +1,362 @@ +. + */ + +namespace Doctrine\DBAL\Driver\IBMDB2; + +use Doctrine\DBAL\Driver\Statement; + +class DB2Statement implements \IteratorAggregate, Statement +{ + /** + * @var resource + */ + private $_stmt = null; + + /** + * @var array + */ + private $_bindParam = array(); + + /** + * @var string Name of the default class to instantiate when fetch mode is \PDO::FETCH_CLASS. + */ + private $defaultFetchClass = '\stdClass'; + + /** + * @var string Constructor arguments for the default class to instantiate when fetch mode is \PDO::FETCH_CLASS. + */ + private $defaultFetchClassCtorArgs = array(); + + /** + * @var integer + */ + private $_defaultFetchMode = \PDO::FETCH_BOTH; + + /** + * Indicates whether the statement is in the state when fetching results is possible + * + * @var bool + */ + private $result = false; + + /** + * DB2_BINARY, DB2_CHAR, DB2_DOUBLE, or DB2_LONG + * + * @var array + */ + static private $_typeMap = array( + \PDO::PARAM_INT => DB2_LONG, + \PDO::PARAM_STR => DB2_CHAR, + ); + + /** + * @param resource $stmt + */ + public function __construct($stmt) + { + $this->_stmt = $stmt; + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = null) + { + return $this->bindParam($param, $value, $type); + } + + /** + * {@inheritdoc} + */ + public function bindParam($column, &$variable, $type = null, $length = null) + { + $this->_bindParam[$column] =& $variable; + + if ($type && isset(self::$_typeMap[$type])) { + $type = self::$_typeMap[$type]; + } else { + $type = DB2_CHAR; + } + + if (!db2_bind_param($this->_stmt, $column, "variable", DB2_PARAM_IN, $type)) { + throw new DB2Exception(db2_stmt_errormsg()); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function closeCursor() + { + if ( ! $this->_stmt) { + return false; + } + + $this->_bindParam = array(); + + if (!db2_free_result($this->_stmt)) { + return false; + } + + $this->result = false; + + return true; + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + if ( ! $this->_stmt) { + return false; + } + + return db2_num_fields($this->_stmt); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return db2_stmt_error(); + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return array( + 0 => db2_stmt_errormsg(), + 1 => db2_stmt_error(), + ); + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + if ( ! $this->_stmt) { + return false; + } + + if ($params === null) { + ksort($this->_bindParam); + + $params = array(); + + foreach ($this->_bindParam as $column => $value) { + $params[] = $value; + } + } + + $retval = @db2_execute($this->_stmt, $params); + + if ($retval === false) { + throw new DB2Exception(db2_stmt_errormsg()); + } + + $this->result = true; + + return $retval; + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->_defaultFetchMode = $fetchMode; + $this->defaultFetchClass = $arg2 ? $arg2 : $this->defaultFetchClass; + $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs; + + return true; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $data = $this->fetchAll(); + + return new \ArrayIterator($data); + } + + /** + * {@inheritdoc} + */ + public function fetch($fetchMode = null) + { + // do not try fetching from the statement if it's not expected to contain result + // in order to prevent exceptional situation + if (!$this->result) { + return false; + } + + $fetchMode = $fetchMode ?: $this->_defaultFetchMode; + switch ($fetchMode) { + case \PDO::FETCH_BOTH: + return db2_fetch_both($this->_stmt); + case \PDO::FETCH_ASSOC: + return db2_fetch_assoc($this->_stmt); + case \PDO::FETCH_CLASS: + $className = $this->defaultFetchClass; + $ctorArgs = $this->defaultFetchClassCtorArgs; + + if (func_num_args() >= 2) { + $args = func_get_args(); + $className = $args[1]; + $ctorArgs = isset($args[2]) ? $args[2] : array(); + } + + $result = db2_fetch_object($this->_stmt); + + if ($result instanceof \stdClass) { + $result = $this->castObject($result, $className, $ctorArgs); + } + + return $result; + case \PDO::FETCH_NUM: + return db2_fetch_array($this->_stmt); + case \PDO::FETCH_OBJ: + return db2_fetch_object($this->_stmt); + default: + throw new DB2Exception("Given Fetch-Style " . $fetchMode . " is not supported."); + } + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null) + { + $rows = array(); + + switch ($fetchMode) { + case \PDO::FETCH_CLASS: + while ($row = call_user_func_array(array($this, 'fetch'), func_get_args())) { + $rows[] = $row; + } + break; + case \PDO::FETCH_COLUMN: + while ($row = $this->fetchColumn()) { + $rows[] = $row; + } + break; + default: + while ($row = $this->fetch($fetchMode)) { + $rows[] = $row; + } + } + + return $rows; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(\PDO::FETCH_NUM); + + if (false === $row) { + return false; + } + + return isset($row[$columnIndex]) ? $row[$columnIndex] : null; + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + return (@db2_num_rows($this->_stmt))?:0; + } + + /** + * Casts a stdClass object to the given class name mapping its' properties. + * + * @param \stdClass $sourceObject Object to cast from. + * @param string|object $destinationClass Name of the class or class instance to cast to. + * @param array $ctorArgs Arguments to use for constructing the destination class instance. + * + * @return object + * + * @throws DB2Exception + */ + private function castObject(\stdClass $sourceObject, $destinationClass, array $ctorArgs = array()) + { + if ( ! is_string($destinationClass)) { + if ( ! is_object($destinationClass)) { + throw new DB2Exception(sprintf( + 'Destination class has to be of type string or object, %s given.', gettype($destinationClass) + )); + } + } else { + $destinationClass = new \ReflectionClass($destinationClass); + $destinationClass = $destinationClass->newInstanceArgs($ctorArgs); + } + + $sourceReflection = new \ReflectionObject($sourceObject); + $destinationClassReflection = new \ReflectionObject($destinationClass); + /** @var \ReflectionProperty[] $destinationProperties */ + $destinationProperties = array_change_key_case($destinationClassReflection->getProperties(), \CASE_LOWER); + + foreach ($sourceReflection->getProperties() as $sourceProperty) { + $sourceProperty->setAccessible(true); + + $name = $sourceProperty->getName(); + $value = $sourceProperty->getValue($sourceObject); + + // Try to find a case-matching property. + if ($destinationClassReflection->hasProperty($name)) { + $destinationProperty = $destinationClassReflection->getProperty($name); + + $destinationProperty->setAccessible(true); + $destinationProperty->setValue($destinationClass, $value); + + continue; + } + + $name = strtolower($name); + + // Try to find a property without matching case. + // Fallback for the driver returning either all uppercase or all lowercase column names. + if (isset($destinationProperties[$name])) { + $destinationProperty = $destinationProperties[$name]; + + $destinationProperty->setAccessible(true); + $destinationProperty->setValue($destinationClass, $value); + + continue; + } + + $destinationClass->$name = $value; + } + + return $destinationClass; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Driver.php new file mode 100644 index 00000000..8949d2b0 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Driver.php @@ -0,0 +1,49 @@ +. + */ + +namespace Doctrine\DBAL\Driver\Mysqli; + +use Doctrine\DBAL\Driver\AbstractMySQLDriver; +use Doctrine\DBAL\DBALException; + +/** + * @author Kim Hemsø Rasmussen + */ +class Driver extends AbstractMySQLDriver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + try { + return new MysqliConnection($params, $username, $password, $driverOptions); + } catch (MysqliException $e) { + throw DBALException::driverException($this, $e); + } + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'mysqli'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliConnection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliConnection.php new file mode 100644 index 00000000..ae3502c1 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliConnection.php @@ -0,0 +1,266 @@ +. + */ + +namespace Doctrine\DBAL\Driver\Mysqli; + +use Doctrine\DBAL\Driver\Connection as Connection; +use Doctrine\DBAL\Driver\PingableConnection; +use Doctrine\DBAL\Driver\ServerInfoAwareConnection; + +/** + * @author Kim Hemsø Rasmussen + * @author Till Klampaeckel + */ +class MysqliConnection implements Connection, PingableConnection, ServerInfoAwareConnection +{ + /** + * Name of the option to set connection flags + */ + const OPTION_FLAGS = 'flags'; + + /** + * @var \mysqli + */ + private $_conn; + + /** + * @param array $params + * @param string $username + * @param string $password + * @param array $driverOptions + * + * @throws \Doctrine\DBAL\Driver\Mysqli\MysqliException + */ + public function __construct(array $params, $username, $password, array $driverOptions = array()) + { + $port = isset($params['port']) ? $params['port'] : ini_get('mysqli.default_port'); + + // Fallback to default MySQL port if not given. + if ( ! $port) { + $port = 3306; + } + + $socket = isset($params['unix_socket']) ? $params['unix_socket'] : ini_get('mysqli.default_socket'); + $dbname = isset($params['dbname']) ? $params['dbname'] : null; + + $flags = isset($driverOptions[static::OPTION_FLAGS]) ? $driverOptions[static::OPTION_FLAGS] : null; + + $this->_conn = mysqli_init(); + + $this->setDriverOptions($driverOptions); + + set_error_handler(function () {}); + + if ( ! $this->_conn->real_connect($params['host'], $username, $password, $dbname, $port, $socket, $flags)) { + restore_error_handler(); + + throw new MysqliException($this->_conn->connect_error, @$this->_conn->sqlstate ?: 'HY000', $this->_conn->connect_errno); + } + + restore_error_handler(); + + if (isset($params['charset'])) { + $this->_conn->set_charset($params['charset']); + } + } + + /** + * Retrieves mysqli native resource handle. + * + * Could be used if part of your application is not using DBAL. + * + * @return \mysqli + */ + public function getWrappedResourceHandle() + { + return $this->_conn; + } + + /** + * {@inheritdoc} + */ + public function getServerVersion() + { + $majorVersion = floor($this->_conn->server_version / 10000); + $minorVersion = floor(($this->_conn->server_version - $majorVersion * 10000) / 100); + $patchVersion = floor($this->_conn->server_version - $majorVersion * 10000 - $minorVersion * 100); + + return $majorVersion . '.' . $minorVersion . '.' . $patchVersion; + } + + /** + * {@inheritdoc} + */ + public function requiresQueryForServerVersion() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function prepare($prepareString) + { + return new MysqliStatement($this->_conn, $prepareString); + } + + /** + * {@inheritdoc} + */ + public function query() + { + $args = func_get_args(); + $sql = $args[0]; + $stmt = $this->prepare($sql); + $stmt->execute(); + + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function quote($input, $type=\PDO::PARAM_STR) + { + return "'". $this->_conn->escape_string($input) ."'"; + } + + /** + * {@inheritdoc} + */ + public function exec($statement) + { + if (false === $this->_conn->query($statement)) { + throw new MysqliException($this->_conn->error, $this->_conn->sqlstate, $this->_conn->errno); + } + + return $this->_conn->affected_rows; + } + + /** + * {@inheritdoc} + */ + public function lastInsertId($name = null) + { + return $this->_conn->insert_id; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->_conn->query('START TRANSACTION'); + + return true; + } + + /** + * {@inheritdoc} + */ + public function commit() + { + return $this->_conn->commit(); + } + + /** + * {@inheritdoc}non-PHPdoc) + */ + public function rollBack() + { + return $this->_conn->rollback(); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return $this->_conn->errno; + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return $this->_conn->error; + } + + /** + * Apply the driver options to the connection. + * + * @param array $driverOptions + * + * @throws MysqliException When one of of the options is not supported. + * @throws MysqliException When applying doesn't work - e.g. due to incorrect value. + */ + private function setDriverOptions(array $driverOptions = array()) + { + $supportedDriverOptions = array( + \MYSQLI_OPT_CONNECT_TIMEOUT, + \MYSQLI_OPT_LOCAL_INFILE, + \MYSQLI_INIT_COMMAND, + \MYSQLI_READ_DEFAULT_FILE, + \MYSQLI_READ_DEFAULT_GROUP, + ); + + if (defined('MYSQLI_SERVER_PUBLIC_KEY')) { + $supportedDriverOptions[] = \MYSQLI_SERVER_PUBLIC_KEY; + } + + $exceptionMsg = "%s option '%s' with value '%s'"; + + foreach ($driverOptions as $option => $value) { + + if ($option === static::OPTION_FLAGS) { + continue; + } + + if (!in_array($option, $supportedDriverOptions, true)) { + throw new MysqliException( + sprintf($exceptionMsg, 'Unsupported', $option, $value) + ); + } + + if (@mysqli_options($this->_conn, $option, $value)) { + continue; + } + + $msg = sprintf($exceptionMsg, 'Failed to set', $option, $value); + $msg .= sprintf(', error: %s (%d)', mysqli_error($this->_conn), mysqli_errno($this->_conn)); + + throw new MysqliException( + $msg, + $this->_conn->sqlstate, + $this->_conn->errno + ); + } + } + + /** + * Pings the server and re-connects when `mysqli.reconnect = 1` + * + * @return bool + */ + public function ping() + { + return $this->_conn->ping(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php new file mode 100644 index 00000000..23728b11 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php @@ -0,0 +1,32 @@ +. + */ + +namespace Doctrine\DBAL\Driver\Mysqli; + +use Doctrine\DBAL\Driver\AbstractDriverException; + +/** + * Exception thrown in case the mysqli driver errors. + * + * @author Kim Hemsø Rasmussen + * @author Steve Müller + */ +class MysqliException extends AbstractDriverException +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php new file mode 100644 index 00000000..d181949e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php @@ -0,0 +1,403 @@ +. + */ + +namespace Doctrine\DBAL\Driver\Mysqli; + +use Doctrine\DBAL\Driver\Statement; +use PDO; + +/** + * @author Kim Hemsø Rasmussen + */ +class MysqliStatement implements \IteratorAggregate, Statement +{ + /** + * @var array + */ + protected static $_paramTypeMap = array( + PDO::PARAM_STR => 's', + PDO::PARAM_BOOL => 'i', + PDO::PARAM_NULL => 's', + PDO::PARAM_INT => 'i', + PDO::PARAM_LOB => 's' // TODO Support LOB bigger then max package size. + ); + + /** + * @var \mysqli + */ + protected $_conn; + + /** + * @var \mysqli_stmt + */ + protected $_stmt; + + /** + * @var null|boolean|array + */ + protected $_columnNames; + + /** + * @var null|array + */ + protected $_rowBindedValues; + + /** + * @var array + */ + protected $_bindedValues; + + /** + * @var string + */ + protected $types; + + /** + * Contains ref values for bindValue(). + * + * @var array + */ + protected $_values = array(); + + /** + * @var integer + */ + protected $_defaultFetchMode = PDO::FETCH_BOTH; + + /** + * Indicates whether the statement is in the state when fetching results is possible + * + * @var bool + */ + private $result = false; + + /** + * @param \mysqli $conn + * @param string $prepareString + * + * @throws \Doctrine\DBAL\Driver\Mysqli\MysqliException + */ + public function __construct(\mysqli $conn, $prepareString) + { + $this->_conn = $conn; + $this->_stmt = $conn->prepare($prepareString); + if (false === $this->_stmt) { + throw new MysqliException($this->_conn->error, $this->_conn->sqlstate, $this->_conn->errno); + } + + $paramCount = $this->_stmt->param_count; + if (0 < $paramCount) { + $this->types = str_repeat('s', $paramCount); + $this->_bindedValues = array_fill(1, $paramCount, null); + } + } + + /** + * {@inheritdoc} + */ + public function bindParam($column, &$variable, $type = null, $length = null) + { + if (null === $type) { + $type = 's'; + } else { + if (isset(self::$_paramTypeMap[$type])) { + $type = self::$_paramTypeMap[$type]; + } else { + throw new MysqliException("Unknown type: '{$type}'"); + } + } + + $this->_bindedValues[$column] =& $variable; + $this->types[$column - 1] = $type; + + return true; + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = null) + { + if (null === $type) { + $type = 's'; + } else { + if (isset(self::$_paramTypeMap[$type])) { + $type = self::$_paramTypeMap[$type]; + } else { + throw new MysqliException("Unknown type: '{$type}'"); + } + } + + $this->_values[$param] = $value; + $this->_bindedValues[$param] =& $this->_values[$param]; + $this->types[$param - 1] = $type; + + return true; + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + if (null !== $this->_bindedValues) { + if (null !== $params) { + if ( ! $this->_bindValues($params)) { + throw new MysqliException($this->_stmt->error, $this->_stmt->errno); + } + } else { + if (!call_user_func_array(array($this->_stmt, 'bind_param'), array($this->types) + $this->_bindedValues)) { + throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); + } + } + } + + if ( ! $this->_stmt->execute()) { + throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); + } + + if (null === $this->_columnNames) { + $meta = $this->_stmt->result_metadata(); + if (false !== $meta) { + $columnNames = array(); + foreach ($meta->fetch_fields() as $col) { + $columnNames[] = $col->name; + } + $meta->free(); + + $this->_columnNames = $columnNames; + } else { + $this->_columnNames = false; + } + } + + if (false !== $this->_columnNames) { + // Store result of every execution which has it. Otherwise it will be impossible + // to execute a new statement in case if the previous one has non-fetched rows + // @link http://dev.mysql.com/doc/refman/5.7/en/commands-out-of-sync.html + $this->_stmt->store_result(); + + // Bind row values _after_ storing the result. Otherwise, if mysqli is compiled with libmysql, + // it will have to allocate as much memory as it may be needed for the given column type + // (e.g. for a LONGBLOB field it's 4 gigabytes) + // @link https://bugs.php.net/bug.php?id=51386#1270673122 + // + // Make sure that the values are bound after each execution. Otherwise, if closeCursor() has been + // previously called on the statement, the values are unbound making the statement unusable. + // + // It's also important that row values are bound after _each_ call to store_result(). Otherwise, + // if mysqli is compiled with libmysql, subsequently fetched string values will get truncated + // to the length of the ones fetched during the previous execution. + $this->_rowBindedValues = array_fill(0, count($this->_columnNames), null); + + $refs = array(); + foreach ($this->_rowBindedValues as $key => &$value) { + $refs[$key] =& $value; + } + + if (!call_user_func_array(array($this->_stmt, 'bind_result'), $refs)) { + throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); + } + } + + $this->result = true; + + return true; + } + + /** + * Binds a array of values to bound parameters. + * + * @param array $values + * + * @return boolean + */ + private function _bindValues($values) + { + $params = array(); + $types = str_repeat('s', count($values)); + $params[0] = $types; + + foreach ($values as &$v) { + $params[] =& $v; + } + + return call_user_func_array(array($this->_stmt, 'bind_param'), $params); + } + + /** + * @return boolean|array + */ + private function _fetch() + { + $ret = $this->_stmt->fetch(); + + if (true === $ret) { + $values = array(); + foreach ($this->_rowBindedValues as $v) { + $values[] = $v; + } + + return $values; + } + + return $ret; + } + + /** + * {@inheritdoc} + */ + public function fetch($fetchMode = null) + { + // do not try fetching from the statement if it's not expected to contain result + // in order to prevent exceptional situation + if (!$this->result) { + return false; + } + + $values = $this->_fetch(); + if (null === $values) { + return false; + } + + if (false === $values) { + throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno); + } + + $fetchMode = $fetchMode ?: $this->_defaultFetchMode; + + switch ($fetchMode) { + case PDO::FETCH_NUM: + return $values; + + case PDO::FETCH_ASSOC: + return array_combine($this->_columnNames, $values); + + case PDO::FETCH_BOTH: + $ret = array_combine($this->_columnNames, $values); + $ret += $values; + + return $ret; + + default: + throw new MysqliException("Unknown fetch type '{$fetchMode}'"); + } + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null) + { + $fetchMode = $fetchMode ?: $this->_defaultFetchMode; + + $rows = array(); + if (PDO::FETCH_COLUMN == $fetchMode) { + while (($row = $this->fetchColumn()) !== false) { + $rows[] = $row; + } + } else { + while (($row = $this->fetch($fetchMode)) !== false) { + $rows[] = $row; + } + } + + return $rows; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(PDO::FETCH_NUM); + if (false === $row) { + return false; + } + + return isset($row[$columnIndex]) ? $row[$columnIndex] : null; + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return $this->_stmt->errno; + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return $this->_stmt->error; + } + + /** + * {@inheritdoc} + */ + public function closeCursor() + { + $this->_stmt->free_result(); + $this->result = false; + + return true; + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + if (false === $this->_columnNames) { + return $this->_stmt->affected_rows; + } + + return $this->_stmt->num_rows; + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return $this->_stmt->field_count; + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->_defaultFetchMode = $fetchMode; + + return true; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $data = $this->fetchAll(); + + return new \ArrayIterator($data); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/Driver.php new file mode 100644 index 00000000..1aa1b876 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/Driver.php @@ -0,0 +1,71 @@ +. + */ + +namespace Doctrine\DBAL\Driver\OCI8; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver\AbstractOracleDriver; + +/** + * A Doctrine DBAL driver for the Oracle OCI8 PHP extensions. + * + * @author Roman Borschel + * @since 2.0 + */ +class Driver extends AbstractOracleDriver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + try { + return new OCI8Connection( + $username, + $password, + $this->_constructDsn($params), + isset($params['charset']) ? $params['charset'] : null, + isset($params['sessionMode']) ? $params['sessionMode'] : OCI_DEFAULT, + isset($params['persistent']) ? $params['persistent'] : false + ); + } catch (OCI8Exception $e) { + throw DBALException::driverException($this, $e); + } + } + + /** + * Constructs the Oracle DSN. + * + * @param array $params + * + * @return string The DSN. + */ + protected function _constructDsn(array $params) + { + return $this->getEasyConnectString($params); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'oci8'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php new file mode 100644 index 00000000..a919ddaa --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php @@ -0,0 +1,233 @@ +. + */ + +namespace Doctrine\DBAL\Driver\OCI8; + +use Doctrine\DBAL\Driver\Connection; +use Doctrine\DBAL\Driver\ServerInfoAwareConnection; +use Doctrine\DBAL\Platforms\OraclePlatform; + +/** + * OCI8 implementation of the Connection interface. + * + * @since 2.0 + */ +class OCI8Connection implements Connection, ServerInfoAwareConnection +{ + /** + * @var resource + */ + protected $dbh; + + /** + * @var integer + */ + protected $executeMode = OCI_COMMIT_ON_SUCCESS; + + /** + * Creates a Connection to an Oracle Database using oci8 extension. + * + * @param string $username + * @param string $password + * @param string $db + * @param string|null $charset + * @param integer $sessionMode + * @param boolean $persistent + * + * @throws OCI8Exception + */ + public function __construct($username, $password, $db, $charset = null, $sessionMode = OCI_DEFAULT, $persistent = false) + { + if (!defined('OCI_NO_AUTO_COMMIT')) { + define('OCI_NO_AUTO_COMMIT', 0); + } + + $this->dbh = $persistent + ? @oci_pconnect($username, $password, $db, $charset, $sessionMode) + : @oci_connect($username, $password, $db, $charset, $sessionMode); + + if ( ! $this->dbh) { + throw OCI8Exception::fromErrorInfo(oci_error()); + } + } + + /** + * {@inheritdoc} + * + * @throws \UnexpectedValueException if the version string returned by the database server + * does not contain a parsable version number. + */ + public function getServerVersion() + { + if ( ! preg_match('/\s+(\d+\.\d+\.\d+\.\d+\.\d+)\s+/', oci_server_version($this->dbh), $version)) { + throw new \UnexpectedValueException( + sprintf( + 'Unexpected database version string "%s". Cannot parse an appropriate version number from it. ' . + 'Please report this database version string to the Doctrine team.', + oci_server_version($this->dbh) + ) + ); + } + + return $version[1]; + } + + /** + * {@inheritdoc} + */ + public function requiresQueryForServerVersion() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function prepare($prepareString) + { + return new OCI8Statement($this->dbh, $prepareString, $this); + } + + /** + * {@inheritdoc} + */ + public function query() + { + $args = func_get_args(); + $sql = $args[0]; + //$fetchMode = $args[1]; + $stmt = $this->prepare($sql); + $stmt->execute(); + + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function quote($value, $type=\PDO::PARAM_STR) + { + if (is_int($value) || is_float($value)) { + return $value; + } + $value = str_replace("'", "''", $value); + + return "'" . addcslashes($value, "\000\n\r\\\032") . "'"; + } + + /** + * {@inheritdoc} + */ + public function exec($statement) + { + $stmt = $this->prepare($statement); + $stmt->execute(); + + return $stmt->rowCount(); + } + + /** + * {@inheritdoc} + */ + public function lastInsertId($name = null) + { + if ($name === null) { + return false; + } + + OraclePlatform::assertValidIdentifier($name); + + $sql = 'SELECT ' . $name . '.CURRVAL FROM DUAL'; + $stmt = $this->query($sql); + $result = $stmt->fetch(\PDO::FETCH_ASSOC); + + if ($result === false || !isset($result['CURRVAL'])) { + throw new OCI8Exception("lastInsertId failed: Query was executed but no result was returned."); + } + + return (int) $result['CURRVAL']; + } + + /** + * Returns the current execution mode. + * + * @return integer + */ + public function getExecuteMode() + { + return $this->executeMode; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->executeMode = OCI_NO_AUTO_COMMIT; + + return true; + } + + /** + * {@inheritdoc} + */ + public function commit() + { + if (!oci_commit($this->dbh)) { + throw OCI8Exception::fromErrorInfo($this->errorInfo()); + } + $this->executeMode = OCI_COMMIT_ON_SUCCESS; + + return true; + } + + /** + * {@inheritdoc} + */ + public function rollBack() + { + if (!oci_rollback($this->dbh)) { + throw OCI8Exception::fromErrorInfo($this->errorInfo()); + } + $this->executeMode = OCI_COMMIT_ON_SUCCESS; + + return true; + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + $error = oci_error($this->dbh); + if ($error !== false) { + $error = $error['code']; + } + + return $error; + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return oci_error($this->dbh); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Exception.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Exception.php new file mode 100644 index 00000000..1cd08b8a --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Exception.php @@ -0,0 +1,35 @@ +. + */ + +namespace Doctrine\DBAL\Driver\OCI8; + +use Doctrine\DBAL\Driver\AbstractDriverException; + +class OCI8Exception extends AbstractDriverException +{ + /** + * @param array $error + * + * @return \Doctrine\DBAL\Driver\OCI8\OCI8Exception + */ + public static function fromErrorInfo($error) + { + return new self($error['message'], null, $error['code']); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php new file mode 100644 index 00000000..628b6570 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php @@ -0,0 +1,357 @@ +. + */ + +namespace Doctrine\DBAL\Driver\OCI8; + +use PDO; +use IteratorAggregate; +use Doctrine\DBAL\Driver\Statement; + +/** + * The OCI8 implementation of the Statement interface. + * + * @since 2.0 + * @author Roman Borschel + */ +class OCI8Statement implements \IteratorAggregate, Statement +{ + /** + * @var resource + */ + protected $_dbh; + + /** + * @var resource + */ + protected $_sth; + + /** + * @var \Doctrine\DBAL\Driver\OCI8\OCI8Connection + */ + protected $_conn; + + /** + * @var string + */ + protected static $_PARAM = ':param'; + + /** + * @var array + */ + protected static $fetchModeMap = array( + PDO::FETCH_BOTH => OCI_BOTH, + PDO::FETCH_ASSOC => OCI_ASSOC, + PDO::FETCH_NUM => OCI_NUM, + PDO::FETCH_COLUMN => OCI_NUM, + ); + + /** + * @var integer + */ + protected $_defaultFetchMode = PDO::FETCH_BOTH; + + /** + * @var array + */ + protected $_paramMap = array(); + + /** + * Holds references to bound parameter values. + * + * This is a new requirement for PHP7's oci8 extension that prevents bound values from being garbage collected. + * + * @var array + */ + private $boundValues = array(); + + /** + * Indicates whether the statement is in the state when fetching results is possible + * + * @var bool + */ + private $result = false; + + /** + * Creates a new OCI8Statement that uses the given connection handle and SQL statement. + * + * @param resource $dbh The connection handle. + * @param string $statement The SQL statement. + * @param \Doctrine\DBAL\Driver\OCI8\OCI8Connection $conn + */ + public function __construct($dbh, $statement, OCI8Connection $conn) + { + list($statement, $paramMap) = self::convertPositionalToNamedPlaceholders($statement); + $this->_sth = oci_parse($dbh, $statement); + $this->_dbh = $dbh; + $this->_paramMap = $paramMap; + $this->_conn = $conn; + } + + /** + * Converts positional (?) into named placeholders (:param). + * + * Oracle does not support positional parameters, hence this method converts all + * positional parameters into artificially named parameters. Note that this conversion + * is not perfect. All question marks (?) in the original statement are treated as + * placeholders and converted to a named parameter. + * + * The algorithm uses a state machine with two possible states: InLiteral and NotInLiteral. + * Question marks inside literal strings are therefore handled correctly by this method. + * This comes at a cost, the whole sql statement has to be looped over. + * + * @todo extract into utility class in Doctrine\DBAL\Util namespace + * @todo review and test for lost spaces. we experienced missing spaces with oci8 in some sql statements. + * + * @param string $statement The SQL statement to convert. + * + * @return string + */ + static public function convertPositionalToNamedPlaceholders($statement) + { + $count = 1; + $inLiteral = false; // a valid query never starts with quotes + $stmtLen = strlen($statement); + $paramMap = array(); + for ($i = 0; $i < $stmtLen; $i++) { + if ($statement[$i] == '?' && !$inLiteral) { + // real positional parameter detected + $paramMap[$count] = ":param$count"; + $len = strlen($paramMap[$count]); + $statement = substr_replace($statement, ":param$count", $i, 1); + $i += $len-1; // jump ahead + $stmtLen = strlen($statement); // adjust statement length + ++$count; + } elseif ($statement[$i] == "'" || $statement[$i] == '"') { + $inLiteral = ! $inLiteral; // switch state! + } + } + + return array($statement, $paramMap); + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = null) + { + return $this->bindParam($param, $value, $type, null); + } + + /** + * {@inheritdoc} + */ + public function bindParam($column, &$variable, $type = null, $length = null) + { + $column = isset($this->_paramMap[$column]) ? $this->_paramMap[$column] : $column; + + if ($type == \PDO::PARAM_LOB) { + $lob = oci_new_descriptor($this->_dbh, OCI_D_LOB); + $lob->writeTemporary($variable, OCI_TEMP_BLOB); + + $this->boundValues[$column] =& $lob; + + return oci_bind_by_name($this->_sth, $column, $lob, -1, OCI_B_BLOB); + } elseif ($length !== null) { + $this->boundValues[$column] =& $variable; + + return oci_bind_by_name($this->_sth, $column, $variable, $length); + } + + $this->boundValues[$column] =& $variable; + + return oci_bind_by_name($this->_sth, $column, $variable); + } + + /** + * {@inheritdoc} + */ + public function closeCursor() + { + // not having the result means there's nothing to close + if (!$this->result) { + return true; + } + + oci_cancel($this->_sth); + + $this->result = false; + + return true; + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return oci_num_fields($this->_sth); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + $error = oci_error($this->_sth); + if ($error !== false) { + $error = $error['code']; + } + + return $error; + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return oci_error($this->_sth); + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + if ($params) { + $hasZeroIndex = array_key_exists(0, $params); + foreach ($params as $key => $val) { + if ($hasZeroIndex && is_numeric($key)) { + $this->bindValue($key + 1, $val); + } else { + $this->bindValue($key, $val); + } + } + } + + $ret = @oci_execute($this->_sth, $this->_conn->getExecuteMode()); + if ( ! $ret) { + throw OCI8Exception::fromErrorInfo($this->errorInfo()); + } + + $this->result = true; + + return $ret; + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->_defaultFetchMode = $fetchMode; + + return true; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $data = $this->fetchAll(); + + return new \ArrayIterator($data); + } + + /** + * {@inheritdoc} + */ + public function fetch($fetchMode = null) + { + // do not try fetching from the statement if it's not expected to contain result + // in order to prevent exceptional situation + if (!$this->result) { + return false; + } + + $fetchMode = $fetchMode ?: $this->_defaultFetchMode; + if ( ! isset(self::$fetchModeMap[$fetchMode])) { + throw new \InvalidArgumentException("Invalid fetch style: " . $fetchMode); + } + + return oci_fetch_array($this->_sth, self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | OCI_RETURN_LOBS); + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null) + { + $fetchMode = $fetchMode ?: $this->_defaultFetchMode; + if ( ! isset(self::$fetchModeMap[$fetchMode])) { + throw new \InvalidArgumentException("Invalid fetch style: " . $fetchMode); + } + + $result = array(); + if (self::$fetchModeMap[$fetchMode] === OCI_BOTH) { + while ($row = $this->fetch($fetchMode)) { + $result[] = $row; + } + } else { + $fetchStructure = OCI_FETCHSTATEMENT_BY_ROW; + if ($fetchMode == PDO::FETCH_COLUMN) { + $fetchStructure = OCI_FETCHSTATEMENT_BY_COLUMN; + } + + // do not try fetching from the statement if it's not expected to contain result + // in order to prevent exceptional situation + if (!$this->result) { + return array(); + } + + oci_fetch_all($this->_sth, $result, 0, -1, + self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS); + + if ($fetchMode == PDO::FETCH_COLUMN) { + $result = $result[0]; + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + // do not try fetching from the statement if it's not expected to contain result + // in order to prevent exceptional situation + if (!$this->result) { + return false; + } + + $row = oci_fetch_array($this->_sth, OCI_NUM | OCI_RETURN_NULLS | OCI_RETURN_LOBS); + + if (false === $row) { + return false; + } + + return isset($row[$columnIndex]) ? $row[$columnIndex] : null; + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + return oci_num_rows($this->_sth); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php new file mode 100644 index 00000000..8960de1a --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php @@ -0,0 +1,133 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +use PDO; + +/** + * PDO implementation of the Connection interface. + * Used by all PDO-based drivers. + * + * @since 2.0 + */ +class PDOConnection extends PDO implements Connection, ServerInfoAwareConnection +{ + /** + * @param string $dsn + * @param string|null $user + * @param string|null $password + * @param array|null $options + * + * @throws PDOException in case of an error. + */ + public function __construct($dsn, $user = null, $password = null, array $options = null) + { + try { + parent::__construct($dsn, $user, $password, $options); + $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('Doctrine\DBAL\Driver\PDOStatement', array())); + $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } catch (\PDOException $exception) { + throw new PDOException($exception); + } + } + + /** + * {@inheritdoc} + */ + public function exec($statement) + { + try { + return parent::exec($statement); + } catch (\PDOException $exception) { + throw new PDOException($exception); + } + } + + /** + * {@inheritdoc} + */ + public function getServerVersion() + { + return PDO::getAttribute(PDO::ATTR_SERVER_VERSION); + } + + /** + * {@inheritdoc} + */ + public function prepare($prepareString, $driverOptions = array()) + { + try { + return parent::prepare($prepareString, $driverOptions); + } catch (\PDOException $exception) { + throw new PDOException($exception); + } + } + + /** + * {@inheritdoc} + */ + public function query() + { + $args = func_get_args(); + $argsCount = count($args); + + try { + if ($argsCount == 4) { + return parent::query($args[0], $args[1], $args[2], $args[3]); + } + + if ($argsCount == 3) { + return parent::query($args[0], $args[1], $args[2]); + } + + if ($argsCount == 2) { + return parent::query($args[0], $args[1]); + } + + return parent::query($args[0]); + } catch (\PDOException $exception) { + throw new PDOException($exception); + } + } + + /** + * {@inheritdoc} + */ + public function quote($input, $type = \PDO::PARAM_STR) + { + return parent::quote($input, $type); + } + + /** + * {@inheritdoc} + */ + public function lastInsertId($name = null) + { + return parent::lastInsertId($name); + } + + /** + * {@inheritdoc} + */ + public function requiresQueryForServerVersion() + { + return false; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOException.php new file mode 100644 index 00000000..7a1a02a4 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOException.php @@ -0,0 +1,75 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +/** + * Tiny wrapper for PDOException instances to implement the {@link DriverException} interface. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class PDOException extends \PDOException implements DriverException +{ + /** + * The driver specific error code. + * + * @var integer|string|null + */ + private $errorCode; + + /** + * The SQLSTATE of the driver. + * + * @var string|null + */ + private $sqlState; + + /** + * Constructor. + * + * @param \PDOException $exception The PDO exception to wrap. + */ + public function __construct(\PDOException $exception) + { + parent::__construct($exception->getMessage(), 0, $exception); + + $this->code = $exception->getCode(); + $this->errorInfo = $exception->errorInfo; + $this->errorCode = isset($exception->errorInfo[1]) ? $exception->errorInfo[1] : $exception->getCode(); + $this->sqlState = isset($exception->errorInfo[0]) ? $exception->errorInfo[0] : $exception->getCode(); + } + + /** + * {@inheritdoc} + */ + public function getErrorCode() + { + return $this->errorCode; + } + + /** + * {@inheritdoc} + */ + public function getSQLState() + { + return $this->sqlState; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php new file mode 100644 index 00000000..0b929f72 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php @@ -0,0 +1,83 @@ +. + */ + +namespace Doctrine\DBAL\Driver\PDOIbm; + +use Doctrine\DBAL\Driver\AbstractDB2Driver; +use Doctrine\DBAL\Driver\PDOConnection; + +/** + * Driver for the PDO IBM extension. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class Driver extends AbstractDB2Driver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + $conn = new PDOConnection( + $this->_constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + + return $conn; + } + + /** + * Constructs the IBM PDO DSN. + * + * @param array $params + * + * @return string The DSN. + */ + private function _constructPdoDsn(array $params) + { + $dsn = 'ibm:'; + if (isset($params['host'])) { + $dsn .= 'HOSTNAME=' . $params['host'] . ';'; + } + if (isset($params['port'])) { + $dsn .= 'PORT=' . $params['port'] . ';'; + } + $dsn .= 'PROTOCOL=TCPIP;'; + if (isset($params['dbname'])) { + $dsn .= 'DATABASE=' . $params['dbname'] . ';'; + } + + return $dsn; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'pdo_ibm'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php new file mode 100644 index 00000000..7e3632dd --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php @@ -0,0 +1,89 @@ +. + */ + +namespace Doctrine\DBAL\Driver\PDOMySql; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver\AbstractMySQLDriver; +use Doctrine\DBAL\Driver\PDOConnection; +use PDOException; + +/** + * PDO MySql driver. + * + * @since 2.0 + */ +class Driver extends AbstractMySQLDriver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + try { + $conn = new PDOConnection( + $this->constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + } catch (PDOException $e) { + throw DBALException::driverException($this, $e); + } + + return $conn; + } + + /** + * Constructs the MySql PDO DSN. + * + * @param array $params + * + * @return string The DSN. + */ + protected function constructPdoDsn(array $params) + { + $dsn = 'mysql:'; + if (isset($params['host']) && $params['host'] != '') { + $dsn .= 'host=' . $params['host'] . ';'; + } + if (isset($params['port'])) { + $dsn .= 'port=' . $params['port'] . ';'; + } + if (isset($params['dbname'])) { + $dsn .= 'dbname=' . $params['dbname'] . ';'; + } + if (isset($params['unix_socket'])) { + $dsn .= 'unix_socket=' . $params['unix_socket'] . ';'; + } + if (isset($params['charset'])) { + $dsn .= 'charset=' . $params['charset'] . ';'; + } + + return $dsn; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'pdo_mysql'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOOracle/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOOracle/Driver.php new file mode 100644 index 00000000..6e958c74 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOOracle/Driver.php @@ -0,0 +1,78 @@ +. + */ + +namespace Doctrine\DBAL\Driver\PDOOracle; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver\AbstractOracleDriver; +use Doctrine\DBAL\Driver\PDOConnection; + +/** + * PDO Oracle driver. + * + * WARNING: This driver gives us segfaults in our testsuites on CLOB and other + * stuff. PDO Oracle is not maintained by Oracle or anyone in the PHP community, + * which leads us to the recommendation to use the "oci8" driver to connect + * to Oracle instead. + */ +class Driver extends AbstractOracleDriver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + try { + return new PDOConnection( + $this->constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + } catch (\PDOException $e) { + throw DBALException::driverException($this, $e); + } + } + + /** + * Constructs the Oracle PDO DSN. + * + * @param array $params + * + * @return string The DSN. + */ + private function constructPdoDsn(array $params) + { + $dsn = 'oci:dbname=' . $this->getEasyConnectString($params); + + if (isset($params['charset'])) { + $dsn .= ';charset=' . $params['charset']; + } + + return $dsn; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'pdo_oracle'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOPgSql/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOPgSql/Driver.php new file mode 100644 index 00000000..d5b0b8dd --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOPgSql/Driver.php @@ -0,0 +1,112 @@ +. + */ + +namespace Doctrine\DBAL\Driver\PDOPgSql; + +use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; +use Doctrine\DBAL\Driver\PDOConnection; +use Doctrine\DBAL\DBALException; +use PDOException; +use PDO; + +/** + * Driver that connects through pdo_pgsql. + * + * @since 2.0 + */ +class Driver extends AbstractPostgreSQLDriver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + try { + $pdo = new PDOConnection( + $this->_constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + + if (defined('PDO::PGSQL_ATTR_DISABLE_PREPARES') + && (! isset($driverOptions[PDO::PGSQL_ATTR_DISABLE_PREPARES]) + || true === $driverOptions[PDO::PGSQL_ATTR_DISABLE_PREPARES] + ) + ) { + $pdo->setAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES, true); + } + + /* defining client_encoding via SET NAMES to avoid inconsistent DSN support + * - the 'client_encoding' connection param only works with postgres >= 9.1 + * - passing client_encoding via the 'options' param breaks pgbouncer support + */ + if (isset($params['charset'])) { + $pdo->query('SET NAMES \''.$params['charset'].'\''); + } + + return $pdo; + } catch (PDOException $e) { + throw DBALException::driverException($this, $e); + } + } + + /** + * Constructs the Postgres PDO DSN. + * + * @param array $params + * + * @return string The DSN. + */ + private function _constructPdoDsn(array $params) + { + $dsn = 'pgsql:'; + + if (isset($params['host']) && $params['host'] != '') { + $dsn .= 'host=' . $params['host'] . ' '; + } + + if (isset($params['port']) && $params['port'] != '') { + $dsn .= 'port=' . $params['port'] . ' '; + } + + if (isset($params['dbname'])) { + $dsn .= 'dbname=' . $params['dbname'] . ' '; + } else { + // Used for temporary connections to allow operations like dropping the database currently connected to. + // Connecting without an explicit database does not work, therefore "postgres" database is used + // as it is certainly present in every server setup. + $dsn .= 'dbname=postgres' . ' '; + } + + if (isset($params['sslmode'])) { + $dsn .= 'sslmode=' . $params['sslmode'] . ' '; + } + + return $dsn; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'pdo_pgsql'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlite/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlite/Driver.php new file mode 100644 index 00000000..3c5382fb --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlite/Driver.php @@ -0,0 +1,98 @@ +. + */ + +namespace Doctrine\DBAL\Driver\PDOSqlite; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver\AbstractSQLiteDriver; +use Doctrine\DBAL\Driver\PDOConnection; +use PDOException; + +/** + * The PDO Sqlite driver. + * + * @since 2.0 + */ +class Driver extends AbstractSQLiteDriver +{ + /** + * @var array + */ + protected $_userDefinedFunctions = array( + 'sqrt' => array('callback' => array('Doctrine\DBAL\Platforms\SqlitePlatform', 'udfSqrt'), 'numArgs' => 1), + 'mod' => array('callback' => array('Doctrine\DBAL\Platforms\SqlitePlatform', 'udfMod'), 'numArgs' => 2), + 'locate' => array('callback' => array('Doctrine\DBAL\Platforms\SqlitePlatform', 'udfLocate'), 'numArgs' => -1), + ); + + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + if (isset($driverOptions['userDefinedFunctions'])) { + $this->_userDefinedFunctions = array_merge( + $this->_userDefinedFunctions, $driverOptions['userDefinedFunctions']); + unset($driverOptions['userDefinedFunctions']); + } + + try { + $pdo = new PDOConnection( + $this->_constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + } catch (PDOException $ex) { + throw DBALException::driverException($this, $ex); + } + + foreach ($this->_userDefinedFunctions as $fn => $data) { + $pdo->sqliteCreateFunction($fn, $data['callback'], $data['numArgs']); + } + + return $pdo; + } + + /** + * Constructs the Sqlite PDO DSN. + * + * @param array $params + * + * @return string The DSN. + */ + protected function _constructPdoDsn(array $params) + { + $dsn = 'sqlite:'; + if (isset($params['path'])) { + $dsn .= $params['path']; + } elseif (isset($params['memory'])) { + $dsn .= ':memory:'; + } + + return $dsn; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'pdo_sqlite'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Connection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Connection.php new file mode 100644 index 00000000..7b9e9f0a --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Connection.php @@ -0,0 +1,69 @@ +. + */ + +namespace Doctrine\DBAL\Driver\PDOSqlsrv; + +use Doctrine\DBAL\Driver\PDOConnection; + +/** + * Sqlsrv Connection implementation. + * + * @since 2.0 + */ +class Connection extends PDOConnection implements \Doctrine\DBAL\Driver\Connection +{ + /** + * {@inheritdoc} + */ + public function __construct($dsn, $user = null, $password = null, array $options = null) + { + parent::__construct($dsn, $user, $password, $options); + $this->setAttribute(\PDO::ATTR_STATEMENT_CLASS, array(Statement::class, array())); + } + + /** + * @override + */ + public function lastInsertId($name = null) + { + if (null === $name) { + return parent::lastInsertId($name); + } + + $stmt = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?'); + $stmt->execute(array($name)); + + return $stmt->fetchColumn(); + } + + /** + * {@inheritDoc} + */ + public function quote($value, $type=\PDO::PARAM_STR) + { + $val = parent::quote($value, $type); + + // Fix for a driver version terminating all values with null byte + if (strpos($val, "\0") !== false) { + $val = substr($val, 0, -1); + } + + return $val; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Driver.php new file mode 100644 index 00000000..64b91f19 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Driver.php @@ -0,0 +1,81 @@ +. + */ + +namespace Doctrine\DBAL\Driver\PDOSqlsrv; + +use Doctrine\DBAL\Driver\AbstractSQLServerDriver; + +/** + * The PDO-based Sqlsrv driver. + * + * @since 2.0 + */ +class Driver extends AbstractSQLServerDriver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + return new Connection( + $this->_constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + } + + /** + * Constructs the Sqlsrv PDO DSN. + * + * @param array $params + * + * @return string The DSN. + */ + private function _constructPdoDsn(array $params) + { + $dsn = 'sqlsrv:server='; + + if (isset($params['host'])) { + $dsn .= $params['host']; + } + + if (isset($params['port']) && !empty($params['port'])) { + $dsn .= ',' . $params['port']; + } + + if (isset($params['dbname'])) { + $dsn .= ';Database=' . $params['dbname']; + } + + if (isset($params['MultipleActiveResultSets'])) { + $dsn .= '; MultipleActiveResultSets=' . ($params['MultipleActiveResultSets'] ? 'true' : 'false'); + } + + return $dsn; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'pdo_sqlsrv'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Statement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Statement.php new file mode 100644 index 00000000..3d1b22a6 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Statement.php @@ -0,0 +1,49 @@ +. + */ + +namespace Doctrine\DBAL\Driver\PDOSqlsrv; + +use Doctrine\DBAL\Driver\PDOStatement; +use PDO; + +/** + * PDO SQL Server Statement + */ +class Statement extends PDOStatement +{ + /** + * {@inheritdoc} + */ + public function bindParam($column, &$variable, $type = PDO::PARAM_STR, $length = null, $driverOptions = null) + { + if ($type === PDO::PARAM_LOB && $driverOptions === null) { + $driverOptions = PDO::SQLSRV_ENCODING_BINARY; + } + + return parent::bindParam($column, $variable, $type, $length, $driverOptions); + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = PDO::PARAM_STR) + { + return $this->bindParam($param, $value, $type); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php new file mode 100644 index 00000000..b6a8cb48 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php @@ -0,0 +1,156 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +/** + * The PDO implementation of the Statement interface. + * Used by all PDO-based drivers. + * + * @since 2.0 + */ +class PDOStatement extends \PDOStatement implements Statement +{ + /** + * Protected constructor. + */ + protected function __construct() + { + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + // This thin wrapper is necessary to shield against the weird signature + // of PDOStatement::setFetchMode(): even if the second and third + // parameters are optional, PHP will not let us remove it from this + // declaration. + try { + if ($arg2 === null && $arg3 === null) { + return parent::setFetchMode($fetchMode); + } + + if ($arg3 === null) { + return parent::setFetchMode($fetchMode, $arg2); + } + + return parent::setFetchMode($fetchMode, $arg2, $arg3); + } catch (\PDOException $exception) { + throw new PDOException($exception); + } + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = \PDO::PARAM_STR) + { + try { + return parent::bindValue($param, $value, $type); + } catch (\PDOException $exception) { + throw new PDOException($exception); + } + } + + /** + * {@inheritdoc} + */ + public function bindParam($column, &$variable, $type = \PDO::PARAM_STR, $length = null, $driverOptions = null) + { + try { + return parent::bindParam($column, $variable, $type, $length, $driverOptions); + } catch (\PDOException $exception) { + throw new PDOException($exception); + } + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + try { + return parent::execute($params); + } catch (\PDOException $exception) { + throw new PDOException($exception); + } + } + + /** + * {@inheritdoc} + */ + public function fetch($fetchMode = null, $cursorOrientation = null, $cursorOffset = null) + { + try { + if ($fetchMode === null && $cursorOrientation === null && $cursorOffset === null) { + return parent::fetch(); + } + + if ($cursorOrientation === null && $cursorOffset === null) { + return parent::fetch($fetchMode); + } + + if ($cursorOffset === null) { + return parent::fetch($fetchMode, $cursorOrientation); + } + + return parent::fetch($fetchMode, $cursorOrientation, $cursorOffset); + } catch (\PDOException $exception) { + throw new PDOException($exception); + } + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) + { + try { + if ($fetchMode === null && $fetchArgument === null && $ctorArgs === null) { + return parent::fetchAll(); + } + + if ($fetchArgument === null && $ctorArgs === null) { + return parent::fetchAll($fetchMode); + } + + if ($ctorArgs === null) { + return parent::fetchAll($fetchMode, $fetchArgument); + } + + return parent::fetchAll($fetchMode, $fetchArgument, $ctorArgs); + } catch (\PDOException $exception) { + throw new PDOException($exception); + } + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + try { + return parent::fetchColumn($columnIndex); + } catch (\PDOException $exception) { + throw new PDOException($exception); + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PingableConnection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PingableConnection.php new file mode 100644 index 00000000..8c85a82c --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PingableConnection.php @@ -0,0 +1,39 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +/** + * An interface for connections which support a "native" ping method. + * + * @link www.doctrine-project.org + * @since 2.5 + * @author Till Klampaeckel + * @author Benjamin Eberlei + */ +interface PingableConnection +{ + /** + * Pings the database server to determine if the connection is still + * available. Return true/false based on if that was successful or not. + * + * @return bool + */ + public function ping(); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/ResultStatement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/ResultStatement.php new file mode 100644 index 00000000..ad76ddb1 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/ResultStatement.php @@ -0,0 +1,95 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +/** + * Interface for the reading part of a prepare statement only. + * + * @author Benjamin Eberlei + */ +interface ResultStatement extends \Traversable +{ + /** + * Closes the cursor, enabling the statement to be executed again. + * + * @return boolean TRUE on success or FALSE on failure. + */ + public function closeCursor(); + + /** + * Returns the number of columns in the result set + * + * @return integer The number of columns in the result set represented + * by the PDOStatement object. If there is no result set, + * this method should return 0. + */ + public function columnCount(); + + /** + * Sets the fetch mode to use while iterating this statement. + * + * @param integer $fetchMode The fetch mode must be one of the PDO::FETCH_* constants. + * @param mixed $arg2 + * @param mixed $arg3 + * + * @return boolean + * + * @see PDO::FETCH_* constants. + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null); + + /** + * Returns the next row of a result set. + * + * @param integer|null $fetchMode Controls how the next row will be returned to the caller. + * The value must be one of the PDO::FETCH_* constants, + * defaulting to PDO::FETCH_BOTH. + * + * @return mixed The return value of this method on success depends on the fetch mode. In all cases, FALSE is + * returned on failure. + * + * @see PDO::FETCH_* constants. + */ + public function fetch($fetchMode = null); + + /** + * Returns an array containing all of the result set rows. + * + * @param integer|null $fetchMode Controls how the next row will be returned to the caller. + * The value must be one of the PDO::FETCH_* constants, + * defaulting to PDO::FETCH_BOTH. + * + * @return array + * + * @see PDO::FETCH_* constants. + */ + public function fetchAll($fetchMode = null); + + /** + * Returns a single column from the next row of a result set or FALSE if there are no more rows. + * + * @param integer $columnIndex 0-indexed number of the column you wish to retrieve from the row. + * If no value is supplied, PDOStatement->fetchColumn() + * fetches the first column. + * + * @return string|boolean A single column in the next row of a result set, or FALSE if there are no more rows. + */ + public function fetchColumn($columnIndex = 0); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/Driver.php new file mode 100644 index 00000000..cb694a32 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/Driver.php @@ -0,0 +1,104 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLAnywhere; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver\AbstractSQLAnywhereDriver; + +/** + * A Doctrine DBAL driver for the SAP Sybase SQL Anywhere PHP extension. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class Driver extends AbstractSQLAnywhereDriver +{ + /** + * {@inheritdoc} + * + * @throws \Doctrine\DBAL\DBALException if there was a problem establishing the connection. + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + try { + return new SQLAnywhereConnection( + $this->buildDsn( + isset($params['host']) ? $params['host'] : null, + isset($params['port']) ? $params['port'] : null, + isset($params['server']) ? $params['server'] : null, + isset($params['dbname']) ? $params['dbname'] : null, + $username, + $password, + $driverOptions + ), + isset($params['persistent']) ? $params['persistent'] : false + ); + } catch (SQLAnywhereException $e) { + throw DBALException::driverException($this, $e); + } + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'sqlanywhere'; + } + + /** + * Build the connection string for given connection parameters and driver options. + * + * @param string $host Host address to connect to. + * @param integer $port Port to use for the connection (default to SQL Anywhere standard port 2638). + * @param string $server Database server name on the host to connect to. + * SQL Anywhere allows multiple database server instances on the same host, + * therefore specifying the server instance name to use is mandatory. + * @param string $dbname Name of the database on the server instance to connect to. + * @param string $username User name to use for connection authentication. + * @param string $password Password to use for connection authentication. + * @param array $driverOptions Additional parameters to use for the connection. + * + * @return string + */ + private function buildDsn($host, $port, $server, $dbname, $username = null, $password = null, array $driverOptions = array()) + { + $host = $host ?: 'localhost'; + $port = $port ?: 2638; + + if (! empty($server)) { + $server = ';ServerName=' . $server; + } + + return + 'HOST=' . $host . ':' . $port . + $server . + ';DBN=' . $dbname . + ';UID=' . $username . + ';PWD=' . $password . + ';' . implode( + ';', + array_map(function ($key, $value) { + return $key . '=' . $value; + }, array_keys($driverOptions), $driverOptions) + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereConnection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereConnection.php new file mode 100644 index 00000000..79fbb363 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereConnection.php @@ -0,0 +1,223 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLAnywhere; + +use Doctrine\DBAL\Driver\Connection; +use Doctrine\DBAL\Driver\ServerInfoAwareConnection; + +/** + * SAP Sybase SQL Anywhere implementation of the Connection interface. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywhereConnection implements Connection, ServerInfoAwareConnection +{ + /** + * @var resource The SQL Anywhere connection resource. + */ + private $connection; + + /** + * Constructor. + * + * Connects to database with given connection string. + * + * @param string $dsn The connection string. + * @param boolean $persistent Whether or not to establish a persistent connection. + * + * @throws SQLAnywhereException + */ + public function __construct($dsn, $persistent = false) + { + $this->connection = $persistent ? @sasql_pconnect($dsn) : @sasql_connect($dsn); + + if ( ! is_resource($this->connection) || get_resource_type($this->connection) !== 'SQLAnywhere connection') { + throw SQLAnywhereException::fromSQLAnywhereError(); + } + + // Disable PHP warnings on error. + if ( ! sasql_set_option($this->connection, 'verbose_errors', false)) { + throw SQLAnywhereException::fromSQLAnywhereError($this->connection); + } + + // Enable auto committing by default. + if ( ! sasql_set_option($this->connection, 'auto_commit', 'on')) { + throw SQLAnywhereException::fromSQLAnywhereError($this->connection); + } + + // Enable exact, non-approximated row count retrieval. + if ( ! sasql_set_option($this->connection, 'row_counts', true)) { + throw SQLAnywhereException::fromSQLAnywhereError($this->connection); + } + } + + /** + * {@inheritdoc} + * + * @throws SQLAnywhereException + */ + public function beginTransaction() + { + if ( ! sasql_set_option($this->connection, 'auto_commit', 'off')) { + throw SQLAnywhereException::fromSQLAnywhereError($this->connection); + } + + return true; + } + + /** + * {@inheritdoc} + * + * @throws SQLAnywhereException + */ + public function commit() + { + if ( ! sasql_commit($this->connection)) { + throw SQLAnywhereException::fromSQLAnywhereError($this->connection); + } + + $this->endTransaction(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return sasql_errorcode($this->connection); + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return sasql_error($this->connection); + } + + /** + * {@inheritdoc} + */ + public function exec($statement) + { + $stmt = $this->prepare($statement); + + $stmt->execute(); + + return $stmt->rowCount(); + } + + /** + * {@inheritdoc} + */ + public function getServerVersion() + { + return $this->query("SELECT PROPERTY('ProductVersion')")->fetchColumn(); + } + + /** + * {@inheritdoc} + */ + public function lastInsertId($name = null) + { + if (null === $name) { + return sasql_insert_id($this->connection); + } + + return $this->query('SELECT ' . $name . '.CURRVAL')->fetchColumn(); + } + + /** + * {@inheritdoc} + */ + public function prepare($prepareString) + { + return new SQLAnywhereStatement($this->connection, $prepareString); + } + + /** + * {@inheritdoc} + */ + public function query() + { + $args = func_get_args(); + $stmt = $this->prepare($args[0]); + + $stmt->execute(); + + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function quote($input, $type = \PDO::PARAM_STR) + { + if (is_int($input) || is_float($input)) { + return $input; + } + + return "'" . sasql_escape_string($this->connection, $input) . "'"; + } + + /** + * {@inheritdoc} + */ + public function requiresQueryForServerVersion() + { + return true; + } + + /** + * {@inheritdoc} + * + * @throws SQLAnywhereException + */ + public function rollBack() + { + if ( ! sasql_rollback($this->connection)) { + throw SQLAnywhereException::fromSQLAnywhereError($this->connection); + } + + $this->endTransaction(); + + return true; + } + + /** + * Ends transactional mode and enables auto commit again. + * + * @throws SQLAnywhereException + * + * @return boolean Whether or not ending transactional mode succeeded. + */ + private function endTransaction() + { + if ( ! sasql_set_option($this->connection, 'auto_commit', 'on')) { + throw SQLAnywhereException::fromSQLAnywhereError($this->connection); + } + + return true; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereException.php new file mode 100644 index 00000000..b68777da --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereException.php @@ -0,0 +1,94 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLAnywhere; + +use Doctrine\DBAL\Driver\AbstractDriverException; + +/** + * SAP Sybase SQL Anywhere driver exception. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywhereException extends AbstractDriverException +{ + /** + * Helper method to turn SQL Anywhere error into exception. + * + * @param resource|null $conn The SQL Anywhere connection resource to retrieve the last error from. + * @param resource|null $stmt The SQL Anywhere statement resource to retrieve the last error from. + * + * @return SQLAnywhereException + * + * @throws \InvalidArgumentException + */ + public static function fromSQLAnywhereError($conn = null, $stmt = null) + { + if (null !== $conn && ! (is_resource($conn) && get_resource_type($conn) === 'SQLAnywhere connection')) { + throw new \InvalidArgumentException('Invalid SQL Anywhere connection resource given: ' . $conn); + } + + if (null !== $stmt && ! (is_resource($stmt) && get_resource_type($stmt) === 'SQLAnywhere statement')) { + throw new \InvalidArgumentException('Invalid SQL Anywhere statement resource given: ' . $stmt); + } + + $state = $conn ? sasql_sqlstate($conn) : sasql_sqlstate(); + $code = null; + $message = null; + + /** + * Try retrieving the last error from statement resource if given + */ + if ($stmt) { + $code = sasql_stmt_errno($stmt); + $message = sasql_stmt_error($stmt); + } + + /** + * Try retrieving the last error from the connection resource + * if either the statement resource is not given or the statement + * resource is given but the last error could not be retrieved from it (fallback). + * Depending on the type of error, it is sometimes necessary to retrieve + * it from the connection resource even though it occurred during + * a prepared statement. + */ + if ($conn && ! $code) { + $code = sasql_errorcode($conn); + $message = sasql_error($conn); + } + + /** + * Fallback mode if either no connection resource is given + * or the last error could not be retrieved from the given + * connection / statement resource. + */ + if ( ! $conn || ! $code) { + $code = sasql_errorcode(); + $message = sasql_error(); + } + + if ($message) { + return new self('SQLSTATE [' . $state . '] [' . $code . '] ' . $message, $state, $code); + } + + return new self('SQL Anywhere error occurred but no error message was retrieved from driver.', $state, $code); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php new file mode 100644 index 00000000..a6d702a2 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php @@ -0,0 +1,347 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLAnywhere; + +use IteratorAggregate; +use PDO; +use Doctrine\DBAL\Driver\Statement; + +/** + * SAP SQL Anywhere implementation of the Statement interface. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywhereStatement implements IteratorAggregate, Statement +{ + /** + * @var resource The connection resource. + */ + private $conn; + + /** + * @var string Name of the default class to instantiate when fetch mode is \PDO::FETCH_CLASS. + */ + private $defaultFetchClass = '\stdClass'; + + /** + * @var string Constructor arguments for the default class to instantiate when fetch mode is \PDO::FETCH_CLASS. + */ + private $defaultFetchClassCtorArgs = array(); + + /** + * @var int Default fetch mode to use. + */ + private $defaultFetchMode = PDO::FETCH_BOTH; + + /** + * @var resource The result set resource to fetch. + */ + private $result; + + /** + * @var resource The prepared SQL statement to execute. + */ + private $stmt; + + /** + * Constructor. + * + * Prepares given statement for given connection. + * + * @param resource $conn The connection resource to use. + * @param string $sql The SQL statement to prepare. + * + * @throws SQLAnywhereException + */ + public function __construct($conn, $sql) + { + if ( ! is_resource($conn) || get_resource_type($conn) !== 'SQLAnywhere connection') { + throw new SQLAnywhereException('Invalid SQL Anywhere connection resource: ' . $conn); + } + + $this->conn = $conn; + $this->stmt = sasql_prepare($conn, $sql); + + if ( ! is_resource($this->stmt) || get_resource_type($this->stmt) !== 'SQLAnywhere statement') { + throw SQLAnywhereException::fromSQLAnywhereError($conn); + } + } + + /** + * {@inheritdoc} + * + * @throws SQLAnywhereException + */ + public function bindParam($column, &$variable, $type = null, $length = null) + { + switch ($type) { + case PDO::PARAM_INT: + case PDO::PARAM_BOOL: + $type = 'i'; + break; + case PDO::PARAM_LOB: + $type = 'b'; + break; + case PDO::PARAM_NULL: + case PDO::PARAM_STR: + $type = 's'; + break; + default: + throw new SQLAnywhereException('Unknown type: ' . $type); + } + + if ( ! sasql_stmt_bind_param_ex($this->stmt, $column - 1, $variable, $type, $variable === null)) { + throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = null) + { + return $this->bindParam($param, $value, $type); + } + + /** + * {@inheritdoc} + * + * @throws SQLAnywhereException + */ + public function closeCursor() + { + if (!sasql_stmt_reset($this->stmt)) { + throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return sasql_stmt_field_count($this->stmt); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return sasql_stmt_errno($this->stmt); + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return sasql_stmt_error($this->stmt); + } + + /** + * {@inheritdoc} + * + * @throws SQLAnywhereException + */ + public function execute($params = null) + { + if (is_array($params)) { + $hasZeroIndex = array_key_exists(0, $params); + + foreach ($params as $key => $val) { + $key = ($hasZeroIndex && is_numeric($key)) ? $key + 1 : $key; + + $this->bindValue($key, $val); + } + } + + if ( ! sasql_stmt_execute($this->stmt)) { + throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); + } + + $this->result = sasql_stmt_result_metadata($this->stmt); + + return true; + } + + /** + * {@inheritdoc} + * + * @throws SQLAnywhereException + */ + public function fetch($fetchMode = null) + { + if ( ! is_resource($this->result) || get_resource_type($this->result) !== 'SQLAnywhere result') { + return false; + } + + $fetchMode = $fetchMode ?: $this->defaultFetchMode; + + switch ($fetchMode) { + case PDO::FETCH_ASSOC: + return sasql_fetch_assoc($this->result); + case PDO::FETCH_BOTH: + return sasql_fetch_array($this->result, SASQL_BOTH); + case PDO::FETCH_CLASS: + $className = $this->defaultFetchClass; + $ctorArgs = $this->defaultFetchClassCtorArgs; + + if (func_num_args() >= 2) { + $args = func_get_args(); + $className = $args[1]; + $ctorArgs = isset($args[2]) ? $args[2] : array(); + } + + $result = sasql_fetch_object($this->result); + + if ($result instanceof \stdClass) { + $result = $this->castObject($result, $className, $ctorArgs); + } + + return $result; + case PDO::FETCH_NUM: + return sasql_fetch_row($this->result); + case PDO::FETCH_OBJ: + return sasql_fetch_object($this->result); + default: + throw new SQLAnywhereException('Fetch mode is not supported: ' . $fetchMode); + } + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null) + { + $rows = array(); + + switch ($fetchMode) { + case PDO::FETCH_CLASS: + while ($row = call_user_func_array(array($this, 'fetch'), func_get_args())) { + $rows[] = $row; + } + break; + case PDO::FETCH_COLUMN: + while ($row = $this->fetchColumn()) { + $rows[] = $row; + } + break; + default: + while ($row = $this->fetch($fetchMode)) { + $rows[] = $row; + } + } + + return $rows; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(PDO::FETCH_NUM); + + if (false === $row) { + return false; + } + + return isset($row[$columnIndex]) ? $row[$columnIndex] : null; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return new \ArrayIterator($this->fetchAll()); + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + return sasql_stmt_affected_rows($this->stmt); + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->defaultFetchMode = $fetchMode; + $this->defaultFetchClass = $arg2 ? $arg2 : $this->defaultFetchClass; + $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs; + } + + /** + * Casts a stdClass object to the given class name mapping its' properties. + * + * @param \stdClass $sourceObject Object to cast from. + * @param string|object $destinationClass Name of the class or class instance to cast to. + * @param array $ctorArgs Arguments to use for constructing the destination class instance. + * + * @return object + * + * @throws SQLAnywhereException + */ + private function castObject(\stdClass $sourceObject, $destinationClass, array $ctorArgs = array()) + { + if ( ! is_string($destinationClass)) { + if ( ! is_object($destinationClass)) { + throw new SQLAnywhereException(sprintf( + 'Destination class has to be of type string or object, %s given.', gettype($destinationClass) + )); + } + } else { + $destinationClass = new \ReflectionClass($destinationClass); + $destinationClass = $destinationClass->newInstanceArgs($ctorArgs); + } + + $sourceReflection = new \ReflectionObject($sourceObject); + $destinationClassReflection = new \ReflectionObject($destinationClass); + + foreach ($sourceReflection->getProperties() as $sourceProperty) { + $sourceProperty->setAccessible(true); + + $name = $sourceProperty->getName(); + $value = $sourceProperty->getValue($sourceObject); + + if ($destinationClassReflection->hasProperty($name)) { + $destinationProperty = $destinationClassReflection->getProperty($name); + + $destinationProperty->setAccessible(true); + $destinationProperty->setValue($destinationClass, $value); + } else { + $destinationClass->$name = $value; + } + } + + return $destinationClass; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php new file mode 100644 index 00000000..f9423973 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php @@ -0,0 +1,68 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLSrv; + +use Doctrine\DBAL\Driver\AbstractSQLServerDriver; + +/** + * Driver for ext/sqlsrv. + */ +class Driver extends AbstractSQLServerDriver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + if (!isset($params['host'])) { + throw new SQLSrvException("Missing 'host' in configuration for sqlsrv driver."); + } + + $serverName = $params['host']; + if (isset($params['port'])) { + $serverName .= ', ' . $params['port']; + } + + if (isset($params['dbname'])) { + $driverOptions['Database'] = $params['dbname']; + } + + if (isset($params['charset'])) { + $driverOptions['CharacterSet'] = $params['charset']; + } + + $driverOptions['UID'] = $username; + $driverOptions['PWD'] = $password; + + if (!isset($driverOptions['ReturnDatesAsStrings'])) { + $driverOptions['ReturnDatesAsStrings'] = 1; + } + + return new SQLSrvConnection($serverName, $driverOptions); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'sqlsrv'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/LastInsertId.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/LastInsertId.php new file mode 100644 index 00000000..67c7bfa8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/LastInsertId.php @@ -0,0 +1,50 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLSrv; + +/** + * Last Id Data Container. + * + * @since 2.3 + * @author Benjamin Eberlei + */ +class LastInsertId +{ + /** + * @var integer + */ + private $id; + + /** + * @param integer $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvConnection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvConnection.php new file mode 100644 index 00000000..9a80b68d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvConnection.php @@ -0,0 +1,192 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLSrv; + +use Doctrine\DBAL\Driver\Connection; +use Doctrine\DBAL\Driver\ServerInfoAwareConnection; + +/** + * SQL Server implementation for the Connection interface. + * + * @since 2.3 + * @author Benjamin Eberlei + */ +class SQLSrvConnection implements Connection, ServerInfoAwareConnection +{ + /** + * @var resource + */ + protected $conn; + + /** + * @var \Doctrine\DBAL\Driver\SQLSrv\LastInsertId + */ + protected $lastInsertId; + + /** + * @param string $serverName + * @param array $connectionOptions + * + * @throws \Doctrine\DBAL\Driver\SQLSrv\SQLSrvException + */ + public function __construct($serverName, $connectionOptions) + { + if ( ! sqlsrv_configure('WarningsReturnAsErrors', 0)) { + throw SQLSrvException::fromSqlSrvErrors(); + } + + $this->conn = sqlsrv_connect($serverName, $connectionOptions); + if ( ! $this->conn) { + throw SQLSrvException::fromSqlSrvErrors(); + } + $this->lastInsertId = new LastInsertId(); + } + + /** + * {@inheritdoc} + */ + public function getServerVersion() + { + $serverInfo = sqlsrv_server_info($this->conn); + + return $serverInfo['SQLServerVersion']; + } + + /** + * {@inheritdoc} + */ + public function requiresQueryForServerVersion() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function prepare($sql) + { + return new SQLSrvStatement($this->conn, $sql, $this->lastInsertId); + } + + /** + * {@inheritDoc} + */ + public function query() + { + $args = func_get_args(); + $sql = $args[0]; + $stmt = $this->prepare($sql); + $stmt->execute(); + + return $stmt; + } + + /** + * {@inheritDoc} + * @license New BSD, code from Zend Framework + */ + public function quote($value, $type=\PDO::PARAM_STR) + { + if (is_int($value)) { + return $value; + } elseif (is_float($value)) { + return sprintf('%F', $value); + } + + return "'" . str_replace("'", "''", $value) . "'"; + } + + /** + * {@inheritDoc} + */ + public function exec($statement) + { + $stmt = $this->prepare($statement); + $stmt->execute(); + + return $stmt->rowCount(); + } + + /** + * {@inheritDoc} + */ + public function lastInsertId($name = null) + { + if ($name !== null) { + $stmt = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?'); + $stmt->execute(array($name)); + + return $stmt->fetchColumn(); + } + + return $this->lastInsertId->getId(); + } + + /** + * {@inheritDoc} + */ + public function beginTransaction() + { + if ( ! sqlsrv_begin_transaction($this->conn)) { + throw SQLSrvException::fromSqlSrvErrors(); + } + } + + /** + * {@inheritDoc} + */ + public function commit() + { + if ( ! sqlsrv_commit($this->conn)) { + throw SQLSrvException::fromSqlSrvErrors(); + } + } + + /** + * {@inheritDoc} + */ + public function rollBack() + { + if ( ! sqlsrv_rollback($this->conn)) { + throw SQLSrvException::fromSqlSrvErrors(); + } + } + + /** + * {@inheritDoc} + */ + public function errorCode() + { + $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); + if ($errors) { + return $errors[0]['code']; + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function errorInfo() + { + return sqlsrv_errors(SQLSRV_ERR_ERRORS); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvException.php new file mode 100644 index 00000000..b2c46325 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvException.php @@ -0,0 +1,56 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLSrv; + + +use Doctrine\DBAL\Driver\AbstractDriverException; + +class SQLSrvException extends AbstractDriverException +{ + /** + * Helper method to turn sql server errors into exception. + * + * @return \Doctrine\DBAL\Driver\SQLSrv\SQLSrvException + */ + static public function fromSqlSrvErrors() + { + $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); + $message = ""; + $sqlState = null; + $errorCode = null; + + foreach ($errors as $error) { + $message .= "SQLSTATE [".$error['SQLSTATE'].", ".$error['code']."]: ". $error['message']."\n"; + + if (null === $sqlState) { + $sqlState = $error['SQLSTATE']; + } + + if (null === $errorCode) { + $errorCode = $error['code']; + } + } + if ( ! $message) { + $message = "SQL Server error occurred but no error message was retrieved from driver."; + } + + return new self(rtrim($message), $sqlState, $errorCode); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php new file mode 100644 index 00000000..eee34179 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php @@ -0,0 +1,384 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLSrv; + +use PDO; +use IteratorAggregate; +use Doctrine\DBAL\Driver\Statement; + +/** + * SQL Server Statement. + * + * @since 2.3 + * @author Benjamin Eberlei + */ +class SQLSrvStatement implements IteratorAggregate, Statement +{ + /** + * The SQLSRV Resource. + * + * @var resource + */ + private $conn; + + /** + * The SQL statement to execute. + * + * @var string + */ + private $sql; + + /** + * The SQLSRV statement resource. + * + * @var resource + */ + private $stmt; + + /** + * References to the variables bound as statement parameters. + * + * @var array + */ + private $variables = array(); + + /** + * Bound parameter types. + * + * @var array + */ + private $types = array(); + + /** + * Translations. + * + * @var array + */ + private static $fetchMap = array( + PDO::FETCH_BOTH => SQLSRV_FETCH_BOTH, + PDO::FETCH_ASSOC => SQLSRV_FETCH_ASSOC, + PDO::FETCH_NUM => SQLSRV_FETCH_NUMERIC, + ); + + /** + * The name of the default class to instantiate when fetch mode is \PDO::FETCH_CLASS. + * + * @var string + */ + private $defaultFetchClass = '\stdClass'; + + /** + * The constructor arguments for the default class to instantiate when fetch mode is \PDO::FETCH_CLASS. + * + * @var string + */ + private $defaultFetchClassCtorArgs = array(); + + /** + * The fetch style. + * + * @param integer + */ + private $defaultFetchMode = PDO::FETCH_BOTH; + + /** + * The last insert ID. + * + * @var \Doctrine\DBAL\Driver\SQLSrv\LastInsertId|null + */ + private $lastInsertId; + + /** + * Indicates whether the statement is in the state when fetching results is possible + * + * @var bool + */ + private $result = false; + + /** + * Append to any INSERT query to retrieve the last insert id. + * + * @var string + */ + const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;'; + + /** + * @param resource $conn + * @param string $sql + * @param \Doctrine\DBAL\Driver\SQLSrv\LastInsertId|null $lastInsertId + */ + public function __construct($conn, $sql, LastInsertId $lastInsertId = null) + { + $this->conn = $conn; + $this->sql = $sql; + + if (stripos($sql, 'INSERT INTO ') === 0) { + $this->sql .= self::LAST_INSERT_ID_SQL; + $this->lastInsertId = $lastInsertId; + } + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = null) + { + if (!is_numeric($param)) { + throw new SQLSrvException( + 'sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.' + ); + } + + $this->variables[$param] = $value; + $this->types[$param] = $type; + } + + /** + * {@inheritdoc} + */ + public function bindParam($column, &$variable, $type = null, $length = null) + { + if (!is_numeric($column)) { + throw new SQLSrvException("sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead."); + } + + $this->variables[$column] =& $variable; + $this->types[$column] = $type; + + // unset the statement resource if it exists as the new one will need to be bound to the new variable + $this->stmt = null; + } + + /** + * {@inheritdoc} + */ + public function closeCursor() + { + // not having the result means there's nothing to close + if (!$this->result) { + return true; + } + + // emulate it by fetching and discarding rows, similarly to what PDO does in this case + // @link http://php.net/manual/en/pdostatement.closecursor.php + // @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075 + // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them + while (sqlsrv_fetch($this->stmt)); + + $this->result = false; + + return true; + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return sqlsrv_num_fields($this->stmt); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); + if ($errors) { + return $errors[0]['code']; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return sqlsrv_errors(SQLSRV_ERR_ERRORS); + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + if ($params) { + $hasZeroIndex = array_key_exists(0, $params); + foreach ($params as $key => $val) { + $key = ($hasZeroIndex && is_numeric($key)) ? $key + 1 : $key; + $this->bindValue($key, $val); + } + } + + if ( ! $this->stmt) { + $this->stmt = $this->prepare(); + } + + if (!sqlsrv_execute($this->stmt)) { + throw SQLSrvException::fromSqlSrvErrors(); + } + + if ($this->lastInsertId) { + sqlsrv_next_result($this->stmt); + sqlsrv_fetch($this->stmt); + $this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0)); + } + + $this->result = true; + } + + /** + * Prepares SQL Server statement resource + * + * @return resource + * @throws SQLSrvException + */ + private function prepare() + { + $params = array(); + + foreach ($this->variables as $column => &$variable) { + if ($this->types[$column] === \PDO::PARAM_LOB) { + $params[$column - 1] = array( + &$variable, + SQLSRV_PARAM_IN, + SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY), + SQLSRV_SQLTYPE_VARBINARY('max'), + ); + } else { + $params[$column - 1] =& $variable; + } + } + + $stmt = sqlsrv_prepare($this->conn, $this->sql, $params); + + if (!$stmt) { + throw SQLSrvException::fromSqlSrvErrors(); + } + + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->defaultFetchMode = $fetchMode; + $this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass; + $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs; + + return true; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $data = $this->fetchAll(); + + return new \ArrayIterator($data); + } + + /** + * {@inheritdoc} + */ + public function fetch($fetchMode = null) + { + // do not try fetching from the statement if it's not expected to contain result + // in order to prevent exceptional situation + if (!$this->result) { + return false; + } + + $args = func_get_args(); + $fetchMode = $fetchMode ?: $this->defaultFetchMode; + + if (isset(self::$fetchMap[$fetchMode])) { + return sqlsrv_fetch_array($this->stmt, self::$fetchMap[$fetchMode]) ?: false; + } + + if ($fetchMode == PDO::FETCH_OBJ || $fetchMode == PDO::FETCH_CLASS) { + $className = $this->defaultFetchClass; + $ctorArgs = $this->defaultFetchClassCtorArgs; + + if (count($args) >= 2) { + $className = $args[1]; + $ctorArgs = (isset($args[2])) ? $args[2] : array(); + } + + return sqlsrv_fetch_object($this->stmt, $className, $ctorArgs) ?: false; + } + + throw new SQLSrvException("Fetch mode is not supported!"); + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null) + { + $rows = array(); + + switch ($fetchMode) { + case PDO::FETCH_CLASS: + while ($row = call_user_func_array(array($this, 'fetch'), func_get_args())) { + $rows[] = $row; + } + break; + case PDO::FETCH_COLUMN: + while ($row = $this->fetchColumn()) { + $rows[] = $row; + } + break; + default: + while ($row = $this->fetch($fetchMode)) { + $rows[] = $row; + } + } + + return $rows; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(PDO::FETCH_NUM); + + if (false === $row) { + return false; + } + + return isset($row[$columnIndex]) ? $row[$columnIndex] : null; + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + return sqlsrv_rows_affected($this->stmt); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/ServerInfoAwareConnection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/ServerInfoAwareConnection.php new file mode 100644 index 00000000..f15ffefc --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/ServerInfoAwareConnection.php @@ -0,0 +1,44 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +/** + * Contract for a connection that is able to provide information about the server it is connected to. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +interface ServerInfoAwareConnection +{ + /** + * Returns the version number of the database server connected to. + * + * @return string + */ + public function getServerVersion(); + + /** + * Checks whether a query is required to retrieve the database server version. + * + * @return boolean True if a query is required to retrieve the database server version, false otherwise. + */ + public function requiresQueryForServerVersion(); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Statement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Statement.php new file mode 100644 index 00000000..aedf47b8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Statement.php @@ -0,0 +1,128 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +/** + * Statement interface. + * Drivers must implement this interface. + * + * This resembles (a subset of) the PDOStatement interface. + * + * @author Konsta Vesterinen + * @author Roman Borschel + * @link www.doctrine-project.org + * @since 2.0 + */ +interface Statement extends ResultStatement +{ + /** + * Binds a value to a corresponding named (not supported by mysqli driver, see comment below) or positional + * placeholder in the SQL statement that was used to prepare the statement. + * + * As mentioned above, the named parameters are not natively supported by the mysqli driver, use executeQuery(), + * fetchAll(), fetchArray(), fetchColumn(), fetchAssoc() methods to have the named parameter emulated by doctrine. + * + * @param mixed $param Parameter identifier. For a prepared statement using named placeholders, + * this will be a parameter name of the form :name. For a prepared statement + * using question mark placeholders, this will be the 1-indexed position of the parameter. + * @param mixed $value The value to bind to the parameter. + * @param integer $type Explicit data type for the parameter using the PDO::PARAM_* constants. + * + * @return boolean TRUE on success or FALSE on failure. + */ + function bindValue($param, $value, $type = null); + + + /** + * Binds a PHP variable to a corresponding named (not supported by mysqli driver, see comment below) or question + * mark placeholder in the SQL statement that was use to prepare the statement. Unlike PDOStatement->bindValue(), + * the variable is bound as a reference and will only be evaluated at the time + * that PDOStatement->execute() is called. + * + * As mentioned above, the named parameters are not natively supported by the mysqli driver, use executeQuery(), + * fetchAll(), fetchArray(), fetchColumn(), fetchAssoc() methods to have the named parameter emulated by doctrine. + * + * Most parameters are input parameters, that is, parameters that are + * used in a read-only fashion to build up the query. Some drivers support the invocation + * of stored procedures that return data as output parameters, and some also as input/output + * parameters that both send in data and are updated to receive it. + * + * @param mixed $column Parameter identifier. For a prepared statement using named placeholders, + * this will be a parameter name of the form :name. For a prepared statement using + * question mark placeholders, this will be the 1-indexed position of the parameter. + * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter. + * @param integer|null $type Explicit data type for the parameter using the PDO::PARAM_* constants. To return + * an INOUT parameter from a stored procedure, use the bitwise OR operator to set the + * PDO::PARAM_INPUT_OUTPUT bits for the data_type parameter. + * @param integer|null $length You must specify maxlength when using an OUT bind + * so that PHP allocates enough memory to hold the returned value. + * + * @return boolean TRUE on success or FALSE on failure. + */ + function bindParam($column, &$variable, $type = null, $length = null); + + /** + * Fetches the SQLSTATE associated with the last operation on the statement handle. + * + * @see Doctrine_Adapter_Interface::errorCode() + * + * @return string The error code string. + */ + function errorCode(); + + /** + * Fetches extended error information associated with the last operation on the statement handle. + * + * @see Doctrine_Adapter_Interface::errorInfo() + * + * @return array The error info array. + */ + function errorInfo(); + + /** + * Executes a prepared statement + * + * If the prepared statement included parameter markers, you must either: + * call PDOStatement->bindParam() to bind PHP variables to the parameter markers: + * bound variables pass their value as input and receive the output value, + * if any, of their associated parameter markers or pass an array of input-only + * parameter values. + * + * + * @param array|null $params An array of values with as many elements as there are + * bound parameters in the SQL statement being executed. + * + * @return boolean TRUE on success or FALSE on failure. + */ + function execute($params = null); + + /** + * Returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement + * executed by the corresponding object. + * + * If the last SQL statement executed by the associated Statement object was a SELECT statement, + * some databases may return the number of rows returned by that statement. However, + * this behaviour is not guaranteed for all databases and should not be + * relied on for portable applications. + * + * @return integer The number of rows. + */ + function rowCount(); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/DriverManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/DriverManager.php new file mode 100644 index 00000000..19aedc37 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/DriverManager.php @@ -0,0 +1,424 @@ +. + */ + +namespace Doctrine\DBAL; + +use Doctrine\Common\EventManager; + +/** + * Factory for creating Doctrine\DBAL\Connection instances. + * + * @author Roman Borschel + * @since 2.0 + */ +final class DriverManager +{ + /** + * List of supported drivers and their mappings to the driver classes. + * + * To add your own driver use the 'driverClass' parameter to + * {@link DriverManager::getConnection()}. + * + * @var array + */ + private static $_driverMap = array( + 'pdo_mysql' => 'Doctrine\DBAL\Driver\PDOMySql\Driver', + 'pdo_sqlite' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver', + 'pdo_pgsql' => 'Doctrine\DBAL\Driver\PDOPgSql\Driver', + 'pdo_oci' => 'Doctrine\DBAL\Driver\PDOOracle\Driver', + 'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver', + 'ibm_db2' => 'Doctrine\DBAL\Driver\IBMDB2\DB2Driver', + 'pdo_sqlsrv' => 'Doctrine\DBAL\Driver\PDOSqlsrv\Driver', + 'mysqli' => 'Doctrine\DBAL\Driver\Mysqli\Driver', + 'drizzle_pdo_mysql' => 'Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver', + 'sqlanywhere' => 'Doctrine\DBAL\Driver\SQLAnywhere\Driver', + 'sqlsrv' => 'Doctrine\DBAL\Driver\SQLSrv\Driver', + ); + + /** + * List of URL schemes from a database URL and their mappings to driver. + */ + private static $driverSchemeAliases = array( + 'db2' => 'ibm_db2', + 'mssql' => 'pdo_sqlsrv', + 'mysql' => 'pdo_mysql', + 'mysql2' => 'pdo_mysql', // Amazon RDS, for some weird reason + 'postgres' => 'pdo_pgsql', + 'postgresql' => 'pdo_pgsql', + 'pgsql' => 'pdo_pgsql', + 'sqlite' => 'pdo_sqlite', + 'sqlite3' => 'pdo_sqlite', + ); + + /** + * Private constructor. This class cannot be instantiated. + */ + private function __construct() + { + } + + /** + * Creates a connection object based on the specified parameters. + * This method returns a Doctrine\DBAL\Connection which wraps the underlying + * driver connection. + * + * $params must contain at least one of the following. + * + * Either 'driver' with one of the following values: + * + * pdo_mysql + * pdo_sqlite + * pdo_pgsql + * pdo_oci (unstable) + * pdo_sqlsrv + * pdo_sqlsrv + * mysqli + * sqlanywhere + * sqlsrv + * ibm_db2 (unstable) + * drizzle_pdo_mysql + * + * OR 'driverClass' that contains the full class name (with namespace) of the + * driver class to instantiate. + * + * Other (optional) parameters: + * + * user (string): + * The username to use when connecting. + * + * password (string): + * The password to use when connecting. + * + * driverOptions (array): + * Any additional driver-specific options for the driver. These are just passed + * through to the driver. + * + * pdo: + * You can pass an existing PDO instance through this parameter. The PDO + * instance will be wrapped in a Doctrine\DBAL\Connection. + * + * wrapperClass: + * You may specify a custom wrapper class through the 'wrapperClass' + * parameter but this class MUST inherit from Doctrine\DBAL\Connection. + * + * driverClass: + * The driver class to use. + * + * @param array $params The parameters. + * @param \Doctrine\DBAL\Configuration|null $config The configuration to use. + * @param \Doctrine\Common\EventManager|null $eventManager The event manager to use. + * + * @return \Doctrine\DBAL\Connection + * + * @throws \Doctrine\DBAL\DBALException + */ + public static function getConnection( + array $params, + Configuration $config = null, + EventManager $eventManager = null) + { + // create default config and event manager, if not set + if ( ! $config) { + $config = new Configuration(); + } + if ( ! $eventManager) { + $eventManager = new EventManager(); + } + + $params = self::parseDatabaseUrl($params); + + // check for existing pdo object + if (isset($params['pdo']) && ! $params['pdo'] instanceof \PDO) { + throw DBALException::invalidPdoInstance(); + } elseif (isset($params['pdo'])) { + $params['pdo']->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $params['driver'] = 'pdo_' . $params['pdo']->getAttribute(\PDO::ATTR_DRIVER_NAME); + } else { + self::_checkParams($params); + } + if (isset($params['driverClass'])) { + $className = $params['driverClass']; + } else { + $className = self::$_driverMap[$params['driver']]; + } + + $driver = new $className(); + + $wrapperClass = 'Doctrine\DBAL\Connection'; + if (isset($params['wrapperClass'])) { + if (is_subclass_of($params['wrapperClass'], $wrapperClass)) { + $wrapperClass = $params['wrapperClass']; + } else { + throw DBALException::invalidWrapperClass($params['wrapperClass']); + } + } + + return new $wrapperClass($params, $driver, $config, $eventManager); + } + + /** + * Returns the list of supported drivers. + * + * @return array + */ + public static function getAvailableDrivers() + { + return array_keys(self::$_driverMap); + } + + /** + * Checks the list of parameters. + * + * @param array $params The list of parameters. + * + * @return void + * + * @throws \Doctrine\DBAL\DBALException + */ + private static function _checkParams(array $params) + { + // check existence of mandatory parameters + + // driver + if ( ! isset($params['driver']) && ! isset($params['driverClass'])) { + throw DBALException::driverRequired(); + } + + // check validity of parameters + + // driver + if (isset($params['driver']) && ! isset(self::$_driverMap[$params['driver']])) { + throw DBALException::unknownDriver($params['driver'], array_keys(self::$_driverMap)); + } + + if (isset($params['driverClass']) && ! in_array('Doctrine\DBAL\Driver', class_implements($params['driverClass'], true))) { + throw DBALException::invalidDriverClass($params['driverClass']); + } + } + + /** + * Normalizes the given connection URL path. + * + * @param string $urlPath + * + * @return string The normalized connection URL path + */ + private static function normalizeDatabaseUrlPath($urlPath) + { + // Trim leading slash from URL path. + return substr($urlPath, 1); + } + + /** + * Extracts parts from a database URL, if present, and returns an + * updated list of parameters. + * + * @param array $params The list of parameters. + * + * @param array A modified list of parameters with info from a database + * URL extracted into indidivual parameter parts. + * + */ + private static function parseDatabaseUrl(array $params) + { + if (!isset($params['url'])) { + return $params; + } + + // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid + $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $params['url']); + + // PHP < 5.4.8 doesn't parse schemeless urls properly. + // See: https://php.net/parse-url#refsect1-function.parse-url-changelog + if (PHP_VERSION_ID < 50408 && strpos($url, '//') === 0) { + $url = parse_url('fake:' . $url); + + unset($url['scheme']); + } else { + $url = parse_url($url); + } + + if ($url === false) { + throw new DBALException('Malformed parameter "url".'); + } + + // If we have a connection URL, we have to unset the default PDO instance connection parameter (if any) + // as we cannot merge connection details from the URL into the PDO instance (URL takes precedence). + unset($params['pdo']); + + $params = self::parseDatabaseUrlScheme($url, $params); + + if (isset($url['host'])) { + $params['host'] = $url['host']; + } + if (isset($url['port'])) { + $params['port'] = $url['port']; + } + if (isset($url['user'])) { + $params['user'] = $url['user']; + } + if (isset($url['pass'])) { + $params['password'] = $url['pass']; + } + + $params = self::parseDatabaseUrlPath($url, $params); + $params = self::parseDatabaseUrlQuery($url, $params); + + return $params; + } + + /** + * Parses the given connection URL and resolves the given connection parameters. + * + * Assumes that the connection URL scheme is already parsed and resolved into the given connection parameters + * via {@link parseDatabaseUrlScheme}. + * + * @param array $url The URL parts to evaluate. + * @param array $params The connection parameters to resolve. + * + * @return array The resolved connection parameters. + * + * @see parseDatabaseUrlScheme + */ + private static function parseDatabaseUrlPath(array $url, array $params) + { + if (! isset($url['path'])) { + return $params; + } + + $url['path'] = self::normalizeDatabaseUrlPath($url['path']); + + // If we do not have a known DBAL driver, we do not know any connection URL path semantics to evaluate + // and therefore treat the path as regular DBAL connection URL path. + if (! isset($params['driver'])) { + return self::parseRegularDatabaseUrlPath($url, $params); + } + + if (strpos($params['driver'], 'sqlite') !== false) { + return self::parseSqliteDatabaseUrlPath($url, $params); + } + + return self::parseRegularDatabaseUrlPath($url, $params); + } + + /** + * Parses the query part of the given connection URL and resolves the given connection parameters. + * + * @param array $url The connection URL parts to evaluate. + * @param array $params The connection parameters to resolve. + * + * @return array The resolved connection parameters. + */ + private static function parseDatabaseUrlQuery(array $url, array $params) + { + if (! isset($url['query'])) { + return $params; + } + + $query = array(); + + parse_str($url['query'], $query); // simply ingest query as extra params, e.g. charset or sslmode + + return array_merge($params, $query); // parse_str wipes existing array elements + } + + /** + * Parses the given regular connection URL and resolves the given connection parameters. + * + * Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}. + * + * @param array $url The regular connection URL parts to evaluate. + * @param array $params The connection parameters to resolve. + * + * @return array The resolved connection parameters. + * + * @see normalizeDatabaseUrlPath + */ + private static function parseRegularDatabaseUrlPath(array $url, array $params) + { + $params['dbname'] = $url['path']; + + return $params; + } + + /** + * Parses the given SQLite connection URL and resolves the given connection parameters. + * + * Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}. + * + * @param array $url The SQLite connection URL parts to evaluate. + * @param array $params The connection parameters to resolve. + * + * @return array The resolved connection parameters. + * + * @see normalizeDatabaseUrlPath + */ + private static function parseSqliteDatabaseUrlPath(array $url, array $params) + { + if ($url['path'] === ':memory:') { + $params['memory'] = true; + + return $params; + } + + $params['path'] = $url['path']; // pdo_sqlite driver uses 'path' instead of 'dbname' key + + return $params; + } + + /** + * Parses the scheme part from given connection URL and resolves the given connection parameters. + * + * @param array $url The connection URL parts to evaluate. + * @param array $params The connection parameters to resolve. + * + * @return array The resolved connection parameters. + * + * @throws DBALException if parsing failed or resolution is not possible. + */ + private static function parseDatabaseUrlScheme(array $url, array $params) + { + if (isset($url['scheme'])) { + // The requested driver from the URL scheme takes precedence + // over the default custom driver from the connection parameters (if any). + unset($params['driverClass']); + + // URL schemes must not contain underscores, but dashes are ok + $driver = str_replace('-', '_', $url['scheme']); + + // The requested driver from the URL scheme takes precedence + // over the default driver from the connection parameters (if any). + $params['driver'] = isset(self::$driverSchemeAliases[$driver]) + // use alias like "postgres", else we just let checkParams decide later + // if the driver exists (for literal "pdo-pgsql" etc) + ? self::$driverSchemeAliases[$driver] + : $driver; + + return $params; + } + + // If a schemeless connection URL is given, we require a default driver or default custom driver + // as connection parameter. + if (! isset($params['driverClass']) && ! isset($params['driver'])) { + throw DBALException::driverRequired($params['url']); + } + + return $params; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/ConnectionEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/ConnectionEventArgs.php new file mode 100644 index 00000000..fd82638e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/ConnectionEventArgs.php @@ -0,0 +1,78 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\Common\EventArgs; +use Doctrine\DBAL\Connection; + +/** + * Event Arguments used when a Driver connection is established inside Doctrine\DBAL\Connection. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Benjamin Eberlei + */ +class ConnectionEventArgs extends EventArgs +{ + /** + * @var \Doctrine\DBAL\Connection + */ + private $_connection; + + /** + * @param \Doctrine\DBAL\Connection $connection + */ + public function __construct(Connection $connection) + { + $this->_connection = $connection; + } + + /** + * @return \Doctrine\DBAL\Connection + */ + public function getConnection() + { + return $this->_connection; + } + + /** + * @return \Doctrine\DBAL\Driver + */ + public function getDriver() + { + return $this->_connection->getDriver(); + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getDatabasePlatform() + { + return $this->_connection->getDatabasePlatform(); + } + + /** + * @return \Doctrine\DBAL\Schema\AbstractSchemaManager + */ + public function getSchemaManager() + { + return $this->_connection->getSchemaManager(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/MysqlSessionInit.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/MysqlSessionInit.php new file mode 100644 index 00000000..bd362c4c --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/MysqlSessionInit.php @@ -0,0 +1,80 @@ +. + */ + +namespace Doctrine\DBAL\Event\Listeners; + +use Doctrine\DBAL\Event\ConnectionEventArgs; +use Doctrine\DBAL\Events; +use Doctrine\Common\EventSubscriber; + +/** + * MySQL Session Init Event Subscriber which allows to set the Client Encoding of the Connection. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Benjamin Eberlei + * @deprecated Use "charset" option to PDO MySQL Connection instead. + */ +class MysqlSessionInit implements EventSubscriber +{ + /** + * The charset. + * + * @var string + */ + private $_charset; + + /** + * The collation, or FALSE if no collation. + * + * @var string|boolean + */ + private $_collation; + + /** + * Configure Charset and Collation options of MySQL Client for each Connection. + * + * @param string $charset The charset. + * @param string|boolean $collation The collation, or FALSE if no collation. + */ + public function __construct($charset = 'utf8', $collation = false) + { + $this->_charset = $charset; + $this->_collation = $collation; + } + + /** + * @param \Doctrine\DBAL\Event\ConnectionEventArgs $args + * + * @return void + */ + public function postConnect(ConnectionEventArgs $args) + { + $collation = ($this->_collation) ? " COLLATE ".$this->_collation : ""; + $args->getConnection()->executeUpdate("SET NAMES ".$this->_charset . $collation); + } + + /** + * {@inheritdoc} + */ + public function getSubscribedEvents() + { + return array(Events::postConnect); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/OracleSessionInit.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/OracleSessionInit.php new file mode 100644 index 00000000..1c7ddec2 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/OracleSessionInit.php @@ -0,0 +1,90 @@ +. + */ + +namespace Doctrine\DBAL\Event\Listeners; + +use Doctrine\DBAL\Event\ConnectionEventArgs; +use Doctrine\DBAL\Events; +use Doctrine\Common\EventSubscriber; + +/** + * Should be used when Oracle Server default environment does not match the Doctrine requirements. + * + * The following environment variables are required for the Doctrine default date format: + * + * NLS_TIME_FORMAT="HH24:MI:SS" + * NLS_DATE_FORMAT="YYYY-MM-DD HH24:MI:SS" + * NLS_TIMESTAMP_FORMAT="YYYY-MM-DD HH24:MI:SS" + * NLS_TIMESTAMP_TZ_FORMAT="YYYY-MM-DD HH24:MI:SS TZH:TZM" + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class OracleSessionInit implements EventSubscriber +{ + /** + * @var array + */ + protected $_defaultSessionVars = array( + 'NLS_TIME_FORMAT' => "HH24:MI:SS", + 'NLS_DATE_FORMAT' => "YYYY-MM-DD HH24:MI:SS", + 'NLS_TIMESTAMP_FORMAT' => "YYYY-MM-DD HH24:MI:SS", + 'NLS_TIMESTAMP_TZ_FORMAT' => "YYYY-MM-DD HH24:MI:SS TZH:TZM", + 'NLS_NUMERIC_CHARACTERS' => ".,", + ); + + /** + * @param array $oracleSessionVars + */ + public function __construct(array $oracleSessionVars = array()) + { + $this->_defaultSessionVars = array_merge($this->_defaultSessionVars, $oracleSessionVars); + } + + /** + * @param \Doctrine\DBAL\Event\ConnectionEventArgs $args + * + * @return void + */ + public function postConnect(ConnectionEventArgs $args) + { + if (count($this->_defaultSessionVars)) { + array_change_key_case($this->_defaultSessionVars, \CASE_UPPER); + $vars = array(); + foreach ($this->_defaultSessionVars as $option => $value) { + if ($option === 'CURRENT_SCHEMA') { + $vars[] = $option . " = " . $value; + } else { + $vars[] = $option . " = '" . $value . "'"; + } + } + $sql = "ALTER SESSION SET ".implode(" ", $vars); + $args->getConnection()->executeUpdate($sql); + } + } + + /** + * {@inheritdoc} + */ + public function getSubscribedEvents() + { + return array(Events::postConnect); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/SQLSessionInit.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/SQLSessionInit.php new file mode 100644 index 00000000..cfa42994 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/SQLSessionInit.php @@ -0,0 +1,66 @@ +. + */ + +namespace Doctrine\DBAL\Event\Listeners; + +use Doctrine\DBAL\Event\ConnectionEventArgs; +use Doctrine\DBAL\Events; +use Doctrine\Common\EventSubscriber; + +/** + * Session init listener for executing a single SQL statement right after a connection is opened. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + */ +class SQLSessionInit implements EventSubscriber +{ + /** + * @var string + */ + protected $sql; + + /** + * @param string $sql + */ + public function __construct($sql) + { + $this->sql = $sql; + } + + /** + * @param \Doctrine\DBAL\Event\ConnectionEventArgs $args + * + * @return void + */ + public function postConnect(ConnectionEventArgs $args) + { + $conn = $args->getConnection(); + $conn->exec($this->sql); + } + + /** + * {@inheritdoc} + */ + public function getSubscribedEvents() + { + return array(Events::postConnect); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableAddColumnEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableAddColumnEventArgs.php new file mode 100644 index 00000000..c4477ad8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableAddColumnEventArgs.php @@ -0,0 +1,114 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\TableDiff; + +/** + * Event Arguments used when SQL queries for adding table columns are generated inside Doctrine\DBAL\Platform\*Platform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaAlterTableAddColumnEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\Column + */ + private $_column; + + /** + * @var \Doctrine\DBAL\Schema\TableDiff + */ + private $_tableDiff; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var array + */ + private $_sql = array(); + + /** + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct(Column $column, TableDiff $tableDiff, AbstractPlatform $platform) + { + $this->_column = $column; + $this->_tableDiff = $tableDiff; + $this->_platform = $platform; + } + + /** + * @return \Doctrine\DBAL\Schema\Column + */ + public function getColumn() + { + return $this->_column; + } + + /** + * @return \Doctrine\DBAL\Schema\TableDiff + */ + public function getTableDiff() + { + return $this->_tableDiff; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string|array $sql + * + * @return \Doctrine\DBAL\Event\SchemaAlterTableAddColumnEventArgs + */ + public function addSql($sql) + { + if (is_array($sql)) { + $this->_sql = array_merge($this->_sql, $sql); + } else { + $this->_sql[] = $sql; + } + + return $this; + } + + /** + * @return array + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableChangeColumnEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableChangeColumnEventArgs.php new file mode 100644 index 00000000..7249b63c --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableChangeColumnEventArgs.php @@ -0,0 +1,114 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\ColumnDiff; +use Doctrine\DBAL\Schema\TableDiff; + +/** + * Event Arguments used when SQL queries for changing table columns are generated inside Doctrine\DBAL\Platform\*Platform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaAlterTableChangeColumnEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\ColumnDiff + */ + private $_columnDiff; + + /** + * @var \Doctrine\DBAL\Schema\TableDiff + */ + private $_tableDiff; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var array + */ + private $_sql = array(); + + /** + * @param \Doctrine\DBAL\Schema\ColumnDiff $columnDiff + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct(ColumnDiff $columnDiff, TableDiff $tableDiff, AbstractPlatform $platform) + { + $this->_columnDiff = $columnDiff; + $this->_tableDiff = $tableDiff; + $this->_platform = $platform; + } + + /** + * @return \Doctrine\DBAL\Schema\ColumnDiff + */ + public function getColumnDiff() + { + return $this->_columnDiff; + } + + /** + * @return \Doctrine\DBAL\Schema\TableDiff + */ + public function getTableDiff() + { + return $this->_tableDiff; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string|array $sql + * + * @return \Doctrine\DBAL\Event\SchemaAlterTableChangeColumnEventArgs + */ + public function addSql($sql) + { + if (is_array($sql)) { + $this->_sql = array_merge($this->_sql, $sql); + } else { + $this->_sql[] = $sql; + } + + return $this; + } + + /** + * @return array + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableEventArgs.php new file mode 100644 index 00000000..384315d5 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableEventArgs.php @@ -0,0 +1,98 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\TableDiff; + +/** + * Event Arguments used when SQL queries for creating tables are generated inside Doctrine\DBAL\Platform\*Platform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaAlterTableEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\TableDiff + */ + private $_tableDiff; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var array + */ + private $_sql = array(); + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct(TableDiff $tableDiff, AbstractPlatform $platform) + { + $this->_tableDiff = $tableDiff; + $this->_platform = $platform; + } + + /** + * @return \Doctrine\DBAL\Schema\TableDiff + */ + public function getTableDiff() + { + return $this->_tableDiff; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string|array $sql + * + * @return \Doctrine\DBAL\Event\SchemaAlterTableEventArgs + */ + public function addSql($sql) + { + if (is_array($sql)) { + $this->_sql = array_merge($this->_sql, $sql); + } else { + $this->_sql[] = $sql; + } + + return $this; + } + + /** + * @return array + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRemoveColumnEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRemoveColumnEventArgs.php new file mode 100644 index 00000000..06f37888 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRemoveColumnEventArgs.php @@ -0,0 +1,114 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\TableDiff; + +/** + * Event Arguments used when SQL queries for removing table columns are generated inside Doctrine\DBAL\Platform\*Platform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaAlterTableRemoveColumnEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\Column + */ + private $_column; + + /** + * @var \Doctrine\DBAL\Schema\TableDiff + */ + private $_tableDiff; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var array + */ + private $_sql = array(); + + /** + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct(Column $column, TableDiff $tableDiff, AbstractPlatform $platform) + { + $this->_column = $column; + $this->_tableDiff = $tableDiff; + $this->_platform = $platform; + } + + /** + * @return \Doctrine\DBAL\Schema\Column + */ + public function getColumn() + { + return $this->_column; + } + + /** + * @return \Doctrine\DBAL\Schema\TableDiff + */ + public function getTableDiff() + { + return $this->_tableDiff; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string|array $sql + * + * @return \Doctrine\DBAL\Event\SchemaAlterTableRemoveColumnEventArgs + */ + public function addSql($sql) + { + if (is_array($sql)) { + $this->_sql = array_merge($this->_sql, $sql); + } else { + $this->_sql[] = $sql; + } + + return $this; + } + + /** + * @return array + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRenameColumnEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRenameColumnEventArgs.php new file mode 100644 index 00000000..94a274c4 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRenameColumnEventArgs.php @@ -0,0 +1,129 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\TableDiff; + +/** + * Event Arguments used when SQL queries for renaming table columns are generated inside Doctrine\DBAL\Platform\*Platform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaAlterTableRenameColumnEventArgs extends SchemaEventArgs +{ + /** + * @var string + */ + private $_oldColumnName; + + /** + * @var \Doctrine\DBAL\Schema\Column + */ + private $_column; + + /** + * @var \Doctrine\DBAL\Schema\TableDiff + */ + private $_tableDiff; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var array + */ + private $_sql = array(); + + /** + * @param string $oldColumnName + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct($oldColumnName, Column $column, TableDiff $tableDiff, AbstractPlatform $platform) + { + $this->_oldColumnName = $oldColumnName; + $this->_column = $column; + $this->_tableDiff = $tableDiff; + $this->_platform = $platform; + } + + /** + * @return string + */ + public function getOldColumnName() + { + return $this->_oldColumnName; + } + + /** + * @return \Doctrine\DBAL\Schema\Column + */ + public function getColumn() + { + return $this->_column; + } + + /** + * @return \Doctrine\DBAL\Schema\TableDiff + */ + public function getTableDiff() + { + return $this->_tableDiff; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string|array $sql + * + * @return \Doctrine\DBAL\Event\SchemaAlterTableRenameColumnEventArgs + */ + public function addSql($sql) + { + if (is_array($sql)) { + $this->_sql = array_merge($this->_sql, $sql); + } else { + $this->_sql[] = $sql; + } + + return $this; + } + + /** + * @return array + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaColumnDefinitionEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaColumnDefinitionEventArgs.php new file mode 100644 index 00000000..14bb8df7 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaColumnDefinitionEventArgs.php @@ -0,0 +1,137 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\Column; + +/** + * Event Arguments used when the portable column definition is generated inside Doctrine\DBAL\Schema\AbstractSchemaManager. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaColumnDefinitionEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\Column|null + */ + private $_column = null; + + /** + * Raw column data as fetched from the database. + * + * @var array + */ + private $_tableColumn; + + /** + * @var string + */ + private $_table; + + /** + * @var string + */ + private $_database; + + /** + * @var \Doctrine\DBAL\Connection + */ + private $_connection; + + /** + * @param array $tableColumn + * @param string $table + * @param string $database + * @param \Doctrine\DBAL\Connection $connection + */ + public function __construct(array $tableColumn, $table, $database, Connection $connection) + { + $this->_tableColumn = $tableColumn; + $this->_table = $table; + $this->_database = $database; + $this->_connection = $connection; + } + + /** + * Allows to clear the column which means the column will be excluded from + * tables column list. + * + * @param null|\Doctrine\DBAL\Schema\Column $column + * + * @return \Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs + */ + public function setColumn(Column $column = null) + { + $this->_column = $column; + + return $this; + } + + /** + * @return \Doctrine\DBAL\Schema\Column|null + */ + public function getColumn() + { + return $this->_column; + } + + /** + * @return array + */ + public function getTableColumn() + { + return $this->_tableColumn; + } + + /** + * @return string + */ + public function getTable() + { + return $this->_table; + } + + /** + * @return string + */ + public function getDatabase() + { + return $this->_database; + } + + /** + * @return \Doctrine\DBAL\Connection + */ + public function getConnection() + { + return $this->_connection; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getDatabasePlatform() + { + return $this->_connection->getDatabasePlatform(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableColumnEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableColumnEventArgs.php new file mode 100644 index 00000000..38b21825 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableColumnEventArgs.php @@ -0,0 +1,114 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\Table; + +/** + * Event Arguments used when SQL queries for creating table columns are generated inside Doctrine\DBAL\Platform\AbstractPlatform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaCreateTableColumnEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\Column + */ + private $_column; + + /** + * @var \Doctrine\DBAL\Schema\Table + */ + private $_table; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var array + */ + private $_sql = array(); + + /** + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\Table $table + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct(Column $column, Table $table, AbstractPlatform $platform) + { + $this->_column = $column; + $this->_table = $table; + $this->_platform = $platform; + } + + /** + * @return \Doctrine\DBAL\Schema\Column + */ + public function getColumn() + { + return $this->_column; + } + + /** + * @return \Doctrine\DBAL\Schema\Table + */ + public function getTable() + { + return $this->_table; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string|array $sql + * + * @return \Doctrine\DBAL\Event\SchemaCreateTableColumnEventArgs + */ + public function addSql($sql) + { + if (is_array($sql)) { + $this->_sql = array_merge($this->_sql, $sql); + } else { + $this->_sql[] = $sql; + } + + return $this; + } + + /** + * @return array + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableEventArgs.php new file mode 100644 index 00000000..175b0e9c --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableEventArgs.php @@ -0,0 +1,128 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Table; + +/** + * Event Arguments used when SQL queries for creating tables are generated inside Doctrine\DBAL\Platform\AbstractPlatform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaCreateTableEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\Table + */ + private $_table; + + /** + * @var array + */ + private $_columns; + + /** + * @var array + */ + private $_options; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var array + */ + private $_sql = array(); + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param array $columns + * @param array $options + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct(Table $table, array $columns, array $options, AbstractPlatform $platform) + { + $this->_table = $table; + $this->_columns = $columns; + $this->_options = $options; + $this->_platform = $platform; + } + + /** + * @return \Doctrine\DBAL\Schema\Table + */ + public function getTable() + { + return $this->_table; + } + + /** + * @return array + */ + public function getColumns() + { + return $this->_columns; + } + + /** + * @return array + */ + public function getOptions() + { + return $this->_options; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string|array $sql + * + * @return \Doctrine\DBAL\Event\SchemaCreateTableEventArgs + */ + public function addSql($sql) + { + if (is_array($sql)) { + $this->_sql = array_merge($this->_sql, $sql); + } else { + $this->_sql[] = $sql; + } + + return $this; + } + + /** + * @return array + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaDropTableEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaDropTableEventArgs.php new file mode 100644 index 00000000..6ffb6442 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaDropTableEventArgs.php @@ -0,0 +1,100 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Table; + +/** + * Event Arguments used when the SQL query for dropping tables are generated inside Doctrine\DBAL\Platform\AbstractPlatform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaDropTableEventArgs extends SchemaEventArgs +{ + /** + * @var string|\Doctrine\DBAL\Schema\Table + */ + private $_table; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var string|null + */ + private $_sql = null; + + /** + * @param string|\Doctrine\DBAL\Schema\Table $table + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @throws \InvalidArgumentException + */ + public function __construct($table, AbstractPlatform $platform) + { + if ( ! $table instanceof Table && !is_string($table)) { + throw new \InvalidArgumentException('SchemaDropTableEventArgs expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); + } + + $this->_table = $table; + $this->_platform = $platform; + } + + /** + * @return string|\Doctrine\DBAL\Schema\Table + */ + public function getTable() + { + return $this->_table; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string $sql + * + * @return \Doctrine\DBAL\Event\SchemaDropTableEventArgs + */ + public function setSql($sql) + { + $this->_sql = $sql; + + return $this; + } + + /** + * @return string|null + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaEventArgs.php new file mode 100644 index 00000000..d9648b64 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaEventArgs.php @@ -0,0 +1,55 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\Common\EventArgs; + +/** + * Base class for schema related events. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaEventArgs extends EventArgs +{ + /** + * @var boolean + */ + private $_preventDefault = false; + + /** + * @return \Doctrine\DBAL\Event\SchemaEventArgs + */ + public function preventDefault() + { + $this->_preventDefault = true; + + return $this; + } + + /** + * @return boolean + */ + public function isDefaultPrevented() + { + return $this->_preventDefault; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaIndexDefinitionEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaIndexDefinitionEventArgs.php new file mode 100644 index 00000000..3fd1d4bf --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaIndexDefinitionEventArgs.php @@ -0,0 +1,121 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\Index; + +/** + * Event Arguments used when the portable index definition is generated inside Doctrine\DBAL\Schema\AbstractSchemaManager. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaIndexDefinitionEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\Index|null + */ + private $_index = null; + + /** + * Raw index data as fetched from the database. + * + * @var array + */ + private $_tableIndex; + + /** + * @var string + */ + private $_table; + + /** + * @var \Doctrine\DBAL\Connection + */ + private $_connection; + + /** + * @param array $tableIndex + * @param string $table + * @param \Doctrine\DBAL\Connection $connection + */ + public function __construct(array $tableIndex, $table, Connection $connection) + { + $this->_tableIndex = $tableIndex; + $this->_table = $table; + $this->_connection = $connection; + } + + /** + * Allows to clear the index which means the index will be excluded from tables index list. + * + * @param null|\Doctrine\DBAL\Schema\Index $index + * + * @return SchemaIndexDefinitionEventArgs + */ + public function setIndex(Index $index = null) + { + $this->_index = $index; + + return $this; + } + + /** + * @return \Doctrine\DBAL\Schema\Index|null + */ + public function getIndex() + { + return $this->_index; + } + + /** + * @return array + */ + public function getTableIndex() + { + return $this->_tableIndex; + } + + /** + * @return string + */ + public function getTable() + { + return $this->_table; + } + + /** + * @return \Doctrine\DBAL\Connection + */ + public function getConnection() + { + return $this->_connection; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getDatabasePlatform() + { + return $this->_connection->getDatabasePlatform(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Events.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Events.php new file mode 100644 index 00000000..0d31504c --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Events.php @@ -0,0 +1,51 @@ +. + */ + +namespace Doctrine\DBAL; + +/** + * Container for all DBAL events. + * + * This class cannot be instantiated. + * + * @author Roman Borschel + * @since 2.0 + */ +final class Events +{ + /** + * Private constructor. This class cannot be instantiated. + */ + private function __construct() + { + } + + const postConnect = 'postConnect'; + + const onSchemaCreateTable = 'onSchemaCreateTable'; + const onSchemaCreateTableColumn = 'onSchemaCreateTableColumn'; + const onSchemaDropTable = 'onSchemaDropTable'; + const onSchemaAlterTable = 'onSchemaAlterTable'; + const onSchemaAlterTableAddColumn = 'onSchemaAlterTableAddColumn'; + const onSchemaAlterTableRemoveColumn = 'onSchemaAlterTableRemoveColumn'; + const onSchemaAlterTableChangeColumn = 'onSchemaAlterTableChangeColumn'; + const onSchemaAlterTableRenameColumn = 'onSchemaAlterTableRenameColumn'; + const onSchemaColumnDefinition = 'onSchemaColumnDefinition'; + const onSchemaIndexDefinition = 'onSchemaIndexDefinition'; +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ConnectionException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ConnectionException.php new file mode 100644 index 00000000..8d976dcb --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ConnectionException.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +/** + * Base class for all connection related errors detected in the driver. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class ConnectionException extends DriverException +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ConstraintViolationException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ConstraintViolationException.php new file mode 100644 index 00000000..8dd1cb69 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ConstraintViolationException.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +/** + * Base class for all constraint violation related errors detected in the driver. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class ConstraintViolationException extends ServerException +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/DatabaseObjectExistsException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/DatabaseObjectExistsException.php new file mode 100644 index 00000000..279aae9a --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/DatabaseObjectExistsException.php @@ -0,0 +1,35 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +/** + * Base class for all already existing database object related errors detected in the driver. + * + * A database object is considered any asset that can be created in a database + * such as schemas, tables, views, sequences, triggers, constraints, indexes, + * functions, stored procedures etc. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class DatabaseObjectExistsException extends ServerException +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/DatabaseObjectNotFoundException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/DatabaseObjectNotFoundException.php new file mode 100644 index 00000000..fbca22d9 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/DatabaseObjectNotFoundException.php @@ -0,0 +1,35 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +/** + * Base class for all unknown database object related errors detected in the driver. + * + * A database object is considered any asset that can be created in a database + * such as schemas, tables, views, sequences, triggers, constraints, indexes, + * functions, stored procedures etc. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class DatabaseObjectNotFoundException extends ServerException +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/DriverException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/DriverException.php new file mode 100644 index 00000000..17c9f3a8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/DriverException.php @@ -0,0 +1,82 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +use Doctrine\DBAL\DBALException; + +/** + * Base class for all errors detected in the driver. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class DriverException extends DBALException +{ + /** + * The previous DBAL driver exception. + * + * @var \Doctrine\DBAL\Driver\DriverException + */ + private $driverException; + + /** + * Constructor. + * + * @param string $message The exception message. + * @param \Doctrine\DBAL\Driver\DriverException $driverException The DBAL driver exception to chain. + */ + public function __construct($message, \Doctrine\DBAL\Driver\DriverException $driverException) + { + $exception = null; + + if ($driverException instanceof \Exception) { + $exception = $driverException; + } + + parent::__construct($message, 0, $exception); + + $this->driverException = $driverException; + } + + /** + * Returns the driver specific error code if given. + * + * Returns null if no error code was given by the driver. + * + * @return integer|string|null + */ + public function getErrorCode() + { + return $this->driverException->getErrorCode(); + } + + /** + * Returns the SQLSTATE the driver was in at the time the error occurred, if given. + * + * Returns null if no SQLSTATE was given by the driver. + * + * @return string|null + */ + public function getSQLState() + { + return $this->driverException->getSQLState(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ForeignKeyConstraintViolationException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ForeignKeyConstraintViolationException.php new file mode 100644 index 00000000..d244855a --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ForeignKeyConstraintViolationException.php @@ -0,0 +1,32 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +/** + * Exception for a foreign key constraint violation detected in the driver. + * + * @author Benjamin Eberlei + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class ForeignKeyConstraintViolationException extends ConstraintViolationException +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/InvalidArgumentException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..5b811227 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/InvalidArgumentException.php @@ -0,0 +1,40 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +use Doctrine\DBAL\DBALException; + +/** + * Exception to be thrown when invalid arguments are passed to any DBAL API + * + * @author Marco Pivetta + * @link www.doctrine-project.org + * @since 2.5 + */ +class InvalidArgumentException extends DBALException +{ + /** + * @return self + */ + public static function fromEmptyCriteria() + { + return new self('Empty criteria was used, expected non-empty criteria'); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/InvalidFieldNameException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/InvalidFieldNameException.php new file mode 100644 index 00000000..e0b1013f --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/InvalidFieldNameException.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +/** + * Exception for an invalid specified field name in a statement detected in the driver. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class InvalidFieldNameException extends ServerException +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/NonUniqueFieldNameException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/NonUniqueFieldNameException.php new file mode 100644 index 00000000..7b214e3b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/NonUniqueFieldNameException.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +/** + * Exception for a non-unique/ambiguous specified field name in a statement detected in the driver. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class NonUniqueFieldNameException extends ServerException +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/NotNullConstraintViolationException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/NotNullConstraintViolationException.php new file mode 100644 index 00000000..a1f45b92 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/NotNullConstraintViolationException.php @@ -0,0 +1,32 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +/** + * Exception for a NOT NULL constraint violation detected in the driver. + * + * @author Benjamin Eberlei + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class NotNullConstraintViolationException extends ConstraintViolationException +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ReadOnlyException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ReadOnlyException.php new file mode 100644 index 00000000..0345e88f --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ReadOnlyException.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +/** + * Exception for a write operation attempt on a read-only database element detected in the driver. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class ReadOnlyException extends ServerException +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ServerException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ServerException.php new file mode 100644 index 00000000..36460ee0 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ServerException.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +/** + * Base class for all server related errors detected in the driver. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class ServerException extends DriverException +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/SyntaxErrorException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/SyntaxErrorException.php new file mode 100644 index 00000000..97b8fd25 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/SyntaxErrorException.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +/** + * Exception for a syntax error in a statement detected in the driver. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SyntaxErrorException extends ServerException +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/TableExistsException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/TableExistsException.php new file mode 100644 index 00000000..c90bfb47 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/TableExistsException.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +/** + * Exception for an already existing table referenced in a statement detected in the driver. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class TableExistsException extends DatabaseObjectExistsException +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/TableNotFoundException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/TableNotFoundException.php new file mode 100644 index 00000000..90793b90 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/TableNotFoundException.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +/** + * Exception for an unknown table referenced in a statement detected in the driver. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class TableNotFoundException extends DatabaseObjectNotFoundException +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/UniqueConstraintViolationException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/UniqueConstraintViolationException.php new file mode 100644 index 00000000..cd117738 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/UniqueConstraintViolationException.php @@ -0,0 +1,32 @@ +. + */ + +namespace Doctrine\DBAL\Exception; + +/** + * Exception for a unique constraint violation detected in the driver. + * + * @author Benjamin Eberlei + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class UniqueConstraintViolationException extends ConstraintViolationException +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGenerator.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGenerator.php new file mode 100644 index 00000000..62d6fcb8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGenerator.php @@ -0,0 +1,165 @@ +. + */ + +namespace Doctrine\DBAL\Id; + +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Connection; + +/** + * Table ID Generator for those poor languages that are missing sequences. + * + * WARNING: The Table Id Generator clones a second independent database + * connection to work correctly. This means using the generator requests that + * generate IDs will have two open database connections. This is necessary to + * be safe from transaction failures in the main connection. Make sure to only + * ever use one TableGenerator otherwise you end up with many connections. + * + * TableID Generator does not work with SQLite. + * + * The TableGenerator does not take care of creating the SQL Table itself. You + * should look at the `TableGeneratorSchemaVisitor` to do this for you. + * Otherwise the schema for a table looks like: + * + * CREATE sequences ( + * sequence_name VARCHAR(255) NOT NULL, + * sequence_value INT NOT NULL DEFAULT '1', + * sequence_increment_by INT NOT NULL DEFAULT '1', + * PRIMARY KEY (table_name) + * ); + * + * Technically this generator works as follows: + * + * 1. Use a robust transaction serialization level. + * 2. Open transaction + * 3. Acquire a read lock on the table row (SELECT .. FOR UPDATE) + * 4. Increment current value by one and write back to database + * 5. Commit transaction + * + * If you are using a sequence_increment_by value that is larger than one the + * ID Generator will keep incrementing values until it hits the incrementation + * gap before issuing another query. + * + * If no row is present for a given sequence a new one will be created with the + * default values 'value' = 1 and 'increment_by' = 1 + * + * @author Benjamin Eberlei + */ +class TableGenerator +{ + /** + * @var \Doctrine\DBAL\Connection + */ + private $conn; + + /** + * @var string + */ + private $generatorTableName; + + /** + * @var array + */ + private $sequences = array(); + + /** + * @param \Doctrine\DBAL\Connection $conn + * @param string $generatorTableName + * + * @throws \Doctrine\DBAL\DBALException + */ + public function __construct(Connection $conn, $generatorTableName = 'sequences') + { + $params = $conn->getParams(); + if ($params['driver'] == 'pdo_sqlite') { + throw new \Doctrine\DBAL\DBALException("Cannot use TableGenerator with SQLite."); + } + $this->conn = DriverManager::getConnection($params, $conn->getConfiguration(), $conn->getEventManager()); + $this->generatorTableName = $generatorTableName; + } + + /** + * Generates the next unused value for the given sequence name. + * + * @param string $sequenceName + * + * @return integer + * + * @throws \Doctrine\DBAL\DBALException + */ + public function nextValue($sequenceName) + { + if (isset($this->sequences[$sequenceName])) { + $value = $this->sequences[$sequenceName]['value']; + $this->sequences[$sequenceName]['value']++; + if ($this->sequences[$sequenceName]['value'] >= $this->sequences[$sequenceName]['max']) { + unset ($this->sequences[$sequenceName]); + } + + return $value; + } + + $this->conn->beginTransaction(); + + try { + $platform = $this->conn->getDatabasePlatform(); + $sql = "SELECT sequence_value, sequence_increment_by " . + "FROM " . $platform->appendLockHint($this->generatorTableName, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE) . " " . + "WHERE sequence_name = ? " . $platform->getWriteLockSQL(); + $stmt = $this->conn->executeQuery($sql, array($sequenceName)); + + if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $row = array_change_key_case($row, CASE_LOWER); + + $value = $row['sequence_value']; + $value++; + + if ($row['sequence_increment_by'] > 1) { + $this->sequences[$sequenceName] = array( + 'value' => $value, + 'max' => $row['sequence_value'] + $row['sequence_increment_by'] + ); + } + + $sql = "UPDATE " . $this->generatorTableName . " ". + "SET sequence_value = sequence_value + sequence_increment_by " . + "WHERE sequence_name = ? AND sequence_value = ?"; + $rows = $this->conn->executeUpdate($sql, array($sequenceName, $row['sequence_value'])); + + if ($rows != 1) { + throw new \Doctrine\DBAL\DBALException("Race-condition detected while updating sequence. Aborting generation"); + } + } else { + $this->conn->insert( + $this->generatorTableName, + array('sequence_name' => $sequenceName, 'sequence_value' => 1, 'sequence_increment_by' => 1) + ); + $value = 1; + } + + $this->conn->commit(); + + } catch (\Exception $e) { + $this->conn->rollback(); + throw new \Doctrine\DBAL\DBALException("Error occurred while generating ID with TableGenerator, aborted generation: " . $e->getMessage(), 0, $e); + } + + return $value; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGeneratorSchemaVisitor.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGeneratorSchemaVisitor.php new file mode 100644 index 00000000..25b06aca --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGeneratorSchemaVisitor.php @@ -0,0 +1,89 @@ +. + */ + +namespace Doctrine\DBAL\Id; + +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\Index; + +class TableGeneratorSchemaVisitor implements \Doctrine\DBAL\Schema\Visitor\Visitor +{ + /** + * @var string + */ + private $generatorTableName; + + /** + * @param string $generatorTableName + */ + public function __construct($generatorTableName = 'sequences') + { + $this->generatorTableName = $generatorTableName; + } + + /** + * {@inheritdoc} + */ + public function acceptSchema(Schema $schema) + { + $table = $schema->createTable($this->generatorTableName); + $table->addColumn('sequence_name', 'string'); + $table->addColumn('sequence_value', 'integer', array('default' => 1)); + $table->addColumn('sequence_increment_by', 'integer', array('default' => 1)); + } + + /** + * {@inheritdoc} + */ + public function acceptTable(Table $table) + { + } + + /** + * {@inheritdoc} + */ + public function acceptColumn(Table $table, Column $column) + { + } + + /** + * {@inheritdoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + } + + /** + * {@inheritdoc} + */ + public function acceptIndex(Table $table, Index $index) + { + } + + /** + * {@inheritdoc} + */ + public function acceptSequence(Sequence $sequence) + { + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/LockMode.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/LockMode.php new file mode 100644 index 00000000..8d78180e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/LockMode.php @@ -0,0 +1,43 @@ +. + */ + +namespace Doctrine\DBAL; + +/** + * Contains all DBAL LockModes. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Benjamin Eberlei + * @author Roman Borschel + */ +class LockMode +{ + const NONE = 0; + const OPTIMISTIC = 1; + const PESSIMISTIC_READ = 2; + const PESSIMISTIC_WRITE = 4; + + /** + * Private constructor. This class cannot be instantiated. + */ + final private function __construct() + { + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/DebugStack.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/DebugStack.php new file mode 100644 index 00000000..b143a3ee --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/DebugStack.php @@ -0,0 +1,78 @@ +. + */ + +namespace Doctrine\DBAL\Logging; + +/** + * Includes executed SQLs in a Debug Stack. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class DebugStack implements SQLLogger +{ + /** + * Executed SQL queries. + * + * @var array + */ + public $queries = array(); + + /** + * If Debug Stack is enabled (log queries) or not. + * + * @var boolean + */ + public $enabled = true; + + /** + * @var float|null + */ + public $start = null; + + /** + * @var integer + */ + public $currentQuery = 0; + + /** + * {@inheritdoc} + */ + public function startQuery($sql, array $params = null, array $types = null) + { + if ($this->enabled) { + $this->start = microtime(true); + $this->queries[++$this->currentQuery] = array('sql' => $sql, 'params' => $params, 'types' => $types, 'executionMS' => 0); + } + } + + /** + * {@inheritdoc} + */ + public function stopQuery() + { + if ($this->enabled) { + $this->queries[$this->currentQuery]['executionMS'] = microtime(true) - $this->start; + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/EchoSQLLogger.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/EchoSQLLogger.php new file mode 100644 index 00000000..8ed04788 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/EchoSQLLogger.php @@ -0,0 +1,56 @@ +. + */ + +namespace Doctrine\DBAL\Logging; + +/** + * A SQL logger that logs to the standard output using echo/var_dump. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class EchoSQLLogger implements SQLLogger +{ + /** + * {@inheritdoc} + */ + public function startQuery($sql, array $params = null, array $types = null) + { + echo $sql . PHP_EOL; + + if ($params) { + var_dump($params); + } + + if ($types) { + var_dump($types); + } + } + + /** + * {@inheritdoc} + */ + public function stopQuery() + { + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/LoggerChain.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/LoggerChain.php new file mode 100644 index 00000000..81845b1b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/LoggerChain.php @@ -0,0 +1,67 @@ +. + */ + +namespace Doctrine\DBAL\Logging; + +/** + * Chains multiple SQLLogger. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Christophe Coevoet + */ +class LoggerChain implements SQLLogger +{ + /** + * @var \Doctrine\DBAL\Logging\SQLLogger[] + */ + private $loggers = array(); + + /** + * Adds a logger in the chain. + * + * @param \Doctrine\DBAL\Logging\SQLLogger $logger + * + * @return void + */ + public function addLogger(SQLLogger $logger) + { + $this->loggers[] = $logger; + } + + /** + * {@inheritdoc} + */ + public function startQuery($sql, array $params = null, array $types = null) + { + foreach ($this->loggers as $logger) { + $logger->startQuery($sql, $params, $types); + } + } + + /** + * {@inheritdoc} + */ + public function stopQuery() + { + foreach ($this->loggers as $logger) { + $logger->stopQuery(); + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/SQLLogger.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/SQLLogger.php new file mode 100644 index 00000000..52491e50 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/SQLLogger.php @@ -0,0 +1,51 @@ +. + */ + +namespace Doctrine\DBAL\Logging; + +/** + * Interface for SQL loggers. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +interface SQLLogger +{ + /** + * Logs a SQL statement somewhere. + * + * @param string $sql The SQL to be executed. + * @param array|null $params The SQL parameters. + * @param array|null $types The SQL parameter types. + * + * @return void + */ + public function startQuery($sql, array $params = null, array $types = null); + + /** + * Marks the last started query as stopped. This can be used for timing of queries. + * + * @return void + */ + public function stopQuery(); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php new file mode 100644 index 00000000..192879ad --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php @@ -0,0 +1,3536 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\Identifier; +use Doctrine\DBAL\Types; +use Doctrine\DBAL\Schema\Constraint; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ColumnDiff; +use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Events; +use Doctrine\Common\EventManager; +use Doctrine\DBAL\Event\SchemaCreateTableEventArgs; +use Doctrine\DBAL\Event\SchemaCreateTableColumnEventArgs; +use Doctrine\DBAL\Event\SchemaDropTableEventArgs; +use Doctrine\DBAL\Event\SchemaAlterTableEventArgs; +use Doctrine\DBAL\Event\SchemaAlterTableAddColumnEventArgs; +use Doctrine\DBAL\Event\SchemaAlterTableRemoveColumnEventArgs; +use Doctrine\DBAL\Event\SchemaAlterTableChangeColumnEventArgs; +use Doctrine\DBAL\Event\SchemaAlterTableRenameColumnEventArgs; + +/** + * Base class for all DatabasePlatforms. The DatabasePlatforms are the central + * point of abstraction of platform-specific behaviors, features and SQL dialects. + * They are a passive source of information. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Lukas Smith (PEAR MDB2 library) + * @author Benjamin Eberlei + * @todo Remove any unnecessary methods. + */ +abstract class AbstractPlatform +{ + /** + * @var integer + */ + const CREATE_INDEXES = 1; + + /** + * @var integer + */ + const CREATE_FOREIGNKEYS = 2; + + /** + * @var string + */ + const DATE_INTERVAL_UNIT_SECOND = 'SECOND'; + + /** + * @var string + */ + const DATE_INTERVAL_UNIT_MINUTE = 'MINUTE'; + + /** + * @var string + */ + const DATE_INTERVAL_UNIT_HOUR = 'HOUR'; + + /** + * @var string + */ + const DATE_INTERVAL_UNIT_DAY = 'DAY'; + + /** + * @var string + */ + const DATE_INTERVAL_UNIT_WEEK = 'WEEK'; + + /** + * @var string + */ + const DATE_INTERVAL_UNIT_MONTH = 'MONTH'; + + /** + * @var string + */ + const DATE_INTERVAL_UNIT_QUARTER = 'QUARTER'; + + /** + * @var string + */ + const DATE_INTERVAL_UNIT_YEAR = 'YEAR'; + + /** + * @var integer + */ + const TRIM_UNSPECIFIED = 0; + + /** + * @var integer + */ + const TRIM_LEADING = 1; + + /** + * @var integer + */ + const TRIM_TRAILING = 2; + + /** + * @var integer + */ + const TRIM_BOTH = 3; + + /** + * @var array|null + */ + protected $doctrineTypeMapping = null; + + /** + * Contains a list of all columns that should generate parseable column comments for type-detection + * in reverse engineering scenarios. + * + * @var array|null + */ + protected $doctrineTypeComments = null; + + /** + * @var \Doctrine\Common\EventManager + */ + protected $_eventManager; + + /** + * Holds the KeywordList instance for the current platform. + * + * @var \Doctrine\DBAL\Platforms\Keywords\KeywordList + */ + protected $_keywords; + + /** + * Constructor. + */ + public function __construct() + { + } + + /** + * Sets the EventManager used by the Platform. + * + * @param \Doctrine\Common\EventManager $eventManager + */ + public function setEventManager(EventManager $eventManager) + { + $this->_eventManager = $eventManager; + } + + /** + * Gets the EventManager used by the Platform. + * + * @return \Doctrine\Common\EventManager + */ + public function getEventManager() + { + return $this->_eventManager; + } + + /** + * Returns the SQL snippet that declares a boolean column. + * + * @param array $columnDef + * + * @return string + */ + abstract public function getBooleanTypeDeclarationSQL(array $columnDef); + + /** + * Returns the SQL snippet that declares a 4 byte integer column. + * + * @param array $columnDef + * + * @return string + */ + abstract public function getIntegerTypeDeclarationSQL(array $columnDef); + + /** + * Returns the SQL snippet that declares an 8 byte integer column. + * + * @param array $columnDef + * + * @return string + */ + abstract public function getBigIntTypeDeclarationSQL(array $columnDef); + + /** + * Returns the SQL snippet that declares a 2 byte integer column. + * + * @param array $columnDef + * + * @return string + */ + abstract public function getSmallIntTypeDeclarationSQL(array $columnDef); + + /** + * Returns the SQL snippet that declares common properties of an integer column. + * + * @param array $columnDef + * + * @return string + */ + abstract protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef); + + /** + * Lazy load Doctrine Type Mappings. + * + * @return void + */ + abstract protected function initializeDoctrineTypeMappings(); + + /** + * Initializes Doctrine Type Mappings with the platform defaults + * and with all additional type mappings. + * + * @return void + */ + private function initializeAllDoctrineTypeMappings() + { + $this->initializeDoctrineTypeMappings(); + + foreach (Type::getTypesMap() as $typeName => $className) { + foreach (Type::getType($typeName)->getMappedDatabaseTypes($this) as $dbType) { + $this->doctrineTypeMapping[$dbType] = $typeName; + } + } + } + + /** + * Returns the SQL snippet used to declare a VARCHAR column type. + * + * @param array $field + * + * @return string + */ + public function getVarcharTypeDeclarationSQL(array $field) + { + if ( !isset($field['length'])) { + $field['length'] = $this->getVarcharDefaultLength(); + } + + $fixed = (isset($field['fixed'])) ? $field['fixed'] : false; + + if ($field['length'] > $this->getVarcharMaxLength()) { + return $this->getClobTypeDeclarationSQL($field); + } + + return $this->getVarcharTypeDeclarationSQLSnippet($field['length'], $fixed); + } + + /** + * Returns the SQL snippet used to declare a BINARY/VARBINARY column type. + * + * @param array $field The column definition. + * + * @return string + */ + public function getBinaryTypeDeclarationSQL(array $field) + { + if ( ! isset($field['length'])) { + $field['length'] = $this->getBinaryDefaultLength(); + } + + $fixed = isset($field['fixed']) ? $field['fixed'] : false; + + if ($field['length'] > $this->getBinaryMaxLength()) { + return $this->getBlobTypeDeclarationSQL($field); + } + + return $this->getBinaryTypeDeclarationSQLSnippet($field['length'], $fixed); + } + + /** + * Returns the SQL snippet to declare a GUID/UUID field. + * + * By default this maps directly to a CHAR(36) and only maps to more + * special datatypes when the underlying databases support this datatype. + * + * @param array $field + * + * @return string + */ + public function getGuidTypeDeclarationSQL(array $field) + { + $field['length'] = 36; + $field['fixed'] = true; + + return $this->getVarcharTypeDeclarationSQL($field); + } + + /** + * Returns the SQL snippet to declare a JSON field. + * + * By default this maps directly to a CLOB and only maps to more + * special datatypes when the underlying databases support this datatype. + * + * @param array $field + * + * @return string + */ + public function getJsonTypeDeclarationSQL(array $field) + { + return $this->getClobTypeDeclarationSQL($field); + } + + /** + * @param integer $length + * @param boolean $fixed + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + throw DBALException::notSupported('VARCHARs not supported by Platform.'); + } + + /** + * Returns the SQL snippet used to declare a BINARY/VARBINARY column type. + * + * @param integer $length The length of the column. + * @param boolean $fixed Whether the column length is fixed. + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) + { + throw DBALException::notSupported('BINARY/VARBINARY column types are not supported by this platform.'); + } + + /** + * Returns the SQL snippet used to declare a CLOB column type. + * + * @param array $field + * + * @return string + */ + abstract public function getClobTypeDeclarationSQL(array $field); + + /** + * Returns the SQL Snippet used to declare a BLOB column type. + * + * @param array $field + * + * @return string + */ + abstract public function getBlobTypeDeclarationSQL(array $field); + + /** + * Gets the name of the platform. + * + * @return string + */ + abstract public function getName(); + + /** + * Registers a doctrine type to be used in conjunction with a column type of this platform. + * + * @param string $dbType + * @param string $doctrineType + * + * @throws \Doctrine\DBAL\DBALException If the type is not found. + */ + public function registerDoctrineTypeMapping($dbType, $doctrineType) + { + if ($this->doctrineTypeMapping === null) { + $this->initializeAllDoctrineTypeMappings(); + } + + if (!Types\Type::hasType($doctrineType)) { + throw DBALException::typeNotFound($doctrineType); + } + + $dbType = strtolower($dbType); + $this->doctrineTypeMapping[$dbType] = $doctrineType; + } + + /** + * Gets the Doctrine type that is mapped for the given database column type. + * + * @param string $dbType + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException + */ + public function getDoctrineTypeMapping($dbType) + { + if ($this->doctrineTypeMapping === null) { + $this->initializeAllDoctrineTypeMappings(); + } + + $dbType = strtolower($dbType); + + if (!isset($this->doctrineTypeMapping[$dbType])) { + throw new \Doctrine\DBAL\DBALException("Unknown database type ".$dbType." requested, " . get_class($this) . " may not support it."); + } + + return $this->doctrineTypeMapping[$dbType]; + } + + /** + * Checks if a database type is currently supported by this platform. + * + * @param string $dbType + * + * @return boolean + */ + public function hasDoctrineTypeMappingFor($dbType) + { + if ($this->doctrineTypeMapping === null) { + $this->initializeAllDoctrineTypeMappings(); + } + + $dbType = strtolower($dbType); + + return isset($this->doctrineTypeMapping[$dbType]); + } + + /** + * Initializes the Doctrine Type comments instance variable for in_array() checks. + * + * @return void + */ + protected function initializeCommentedDoctrineTypes() + { + $this->doctrineTypeComments = array(); + + foreach (Type::getTypesMap() as $typeName => $className) { + $type = Type::getType($typeName); + + if ($type->requiresSQLCommentHint($this)) { + $this->doctrineTypeComments[] = $typeName; + } + } + } + + /** + * Is it necessary for the platform to add a parsable type comment to allow reverse engineering the given type? + * + * @param \Doctrine\DBAL\Types\Type $doctrineType + * + * @return boolean + */ + public function isCommentedDoctrineType(Type $doctrineType) + { + if ($this->doctrineTypeComments === null) { + $this->initializeCommentedDoctrineTypes(); + } + + return in_array($doctrineType->getName(), $this->doctrineTypeComments); + } + + /** + * Marks this type as to be commented in ALTER TABLE and CREATE TABLE statements. + * + * @param string|\Doctrine\DBAL\Types\Type $doctrineType + * + * @return void + */ + public function markDoctrineTypeCommented($doctrineType) + { + if ($this->doctrineTypeComments === null) { + $this->initializeCommentedDoctrineTypes(); + } + + $this->doctrineTypeComments[] = $doctrineType instanceof Type ? $doctrineType->getName() : $doctrineType; + } + + /** + * Gets the comment to append to a column comment that helps parsing this type in reverse engineering. + * + * @param \Doctrine\DBAL\Types\Type $doctrineType + * + * @return string + */ + public function getDoctrineTypeComment(Type $doctrineType) + { + return '(DC2Type:' . $doctrineType->getName() . ')'; + } + + /** + * Gets the comment of a passed column modified by potential doctrine type comment hints. + * + * @param \Doctrine\DBAL\Schema\Column $column + * + * @return string + */ + protected function getColumnComment(Column $column) + { + $comment = $column->getComment(); + + if ($this->isCommentedDoctrineType($column->getType())) { + $comment .= $this->getDoctrineTypeComment($column->getType()); + } + + return $comment; + } + + /** + * Gets the character used for identifier quoting. + * + * @return string + */ + public function getIdentifierQuoteCharacter() + { + return '"'; + } + + /** + * Gets the string portion that starts an SQL comment. + * + * @return string + */ + public function getSqlCommentStartString() + { + return "--"; + } + + /** + * Gets the string portion that ends an SQL comment. + * + * @return string + */ + public function getSqlCommentEndString() + { + return "\n"; + } + + /** + * Gets the maximum length of a varchar field. + * + * @return integer + */ + public function getVarcharMaxLength() + { + return 4000; + } + + /** + * Gets the default length of a varchar field. + * + * @return integer + */ + public function getVarcharDefaultLength() + { + return 255; + } + + /** + * Gets the maximum length of a binary field. + * + * @return integer + */ + public function getBinaryMaxLength() + { + return 4000; + } + + /** + * Gets the default length of a binary field. + * + * @return integer + */ + public function getBinaryDefaultLength() + { + return 255; + } + + /** + * Gets all SQL wildcard characters of the platform. + * + * @return array + */ + public function getWildcards() + { + return array('%', '_'); + } + + /** + * Returns the regular expression operator. + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getRegexpExpression() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the global unique identifier expression. + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getGuidExpression() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL snippet to get the average value of a column. + * + * @param string $column The column to use. + * + * @return string Generated SQL including an AVG aggregate function. + */ + public function getAvgExpression($column) + { + return 'AVG(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the number of rows (without a NULL value) of a column. + * + * If a '*' is used instead of a column the number of selected rows is returned. + * + * @param string|integer $column The column to use. + * + * @return string Generated SQL including a COUNT aggregate function. + */ + public function getCountExpression($column) + { + return 'COUNT(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the highest value of a column. + * + * @param string $column The column to use. + * + * @return string Generated SQL including a MAX aggregate function. + */ + public function getMaxExpression($column) + { + return 'MAX(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the lowest value of a column. + * + * @param string $column The column to use. + * + * @return string Generated SQL including a MIN aggregate function. + */ + public function getMinExpression($column) + { + return 'MIN(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the total sum of a column. + * + * @param string $column The column to use. + * + * @return string Generated SQL including a SUM aggregate function. + */ + public function getSumExpression($column) + { + return 'SUM(' . $column . ')'; + } + + // scalar functions + + /** + * Returns the SQL snippet to get the md5 sum of a field. + * + * Note: Not SQL92, but common functionality. + * + * @param string $column + * + * @return string + */ + public function getMd5Expression($column) + { + return 'MD5(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the length of a text field. + * + * @param string $column + * + * @return string + */ + public function getLengthExpression($column) + { + return 'LENGTH(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the squared value of a column. + * + * @param string $column The column to use. + * + * @return string Generated SQL including an SQRT aggregate function. + */ + public function getSqrtExpression($column) + { + return 'SQRT(' . $column . ')'; + } + + /** + * Returns the SQL snippet to round a numeric field to the number of decimals specified. + * + * @param string $column + * @param integer $decimals + * + * @return string + */ + public function getRoundExpression($column, $decimals = 0) + { + return 'ROUND(' . $column . ', ' . $decimals . ')'; + } + + /** + * Returns the SQL snippet to get the remainder of the division operation $expression1 / $expression2. + * + * @param string $expression1 + * @param string $expression2 + * + * @return string + */ + public function getModExpression($expression1, $expression2) + { + return 'MOD(' . $expression1 . ', ' . $expression2 . ')'; + } + + /** + * Returns the SQL snippet to trim a string. + * + * @param string $str The expression to apply the trim to. + * @param integer $pos The position of the trim (leading/trailing/both). + * @param string|boolean $char The char to trim, has to be quoted already. Defaults to space. + * + * @return string + */ + public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false) + { + $expression = ''; + + switch ($pos) { + case self::TRIM_LEADING: + $expression = 'LEADING '; + break; + + case self::TRIM_TRAILING: + $expression = 'TRAILING '; + break; + + case self::TRIM_BOTH: + $expression = 'BOTH '; + break; + } + + if (false !== $char) { + $expression .= $char . ' '; + } + + if ($pos || false !== $char) { + $expression .= 'FROM '; + } + + return 'TRIM(' . $expression . $str . ')'; + } + + /** + * Returns the SQL snippet to trim trailing space characters from the expression. + * + * @param string $str Literal string or column name. + * + * @return string + */ + public function getRtrimExpression($str) + { + return 'RTRIM(' . $str . ')'; + } + + /** + * Returns the SQL snippet to trim leading space characters from the expression. + * + * @param string $str Literal string or column name. + * + * @return string + */ + public function getLtrimExpression($str) + { + return 'LTRIM(' . $str . ')'; + } + + /** + * Returns the SQL snippet to change all characters from the expression to uppercase, + * according to the current character set mapping. + * + * @param string $str Literal string or column name. + * + * @return string + */ + public function getUpperExpression($str) + { + return 'UPPER(' . $str . ')'; + } + + /** + * Returns the SQL snippet to change all characters from the expression to lowercase, + * according to the current character set mapping. + * + * @param string $str Literal string or column name. + * + * @return string + */ + public function getLowerExpression($str) + { + return 'LOWER(' . $str . ')'; + } + + /** + * Returns the SQL snippet to get the position of the first occurrence of substring $substr in string $str. + * + * @param string $str Literal string. + * @param string $substr Literal string to find. + * @param integer|boolean $startPos Position to start at, beginning of string by default. + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL snippet to get the current system date. + * + * @return string + */ + public function getNowExpression() + { + return 'NOW()'; + } + + /** + * Returns a SQL snippet to get a substring inside an SQL statement. + * + * Note: Not SQL92, but common functionality. + * + * SQLite only supports the 2 parameter variant of this function. + * + * @param string $value An sql string literal or column name/alias. + * @param integer $from Where to start the substring portion. + * @param integer|null $length The substring portion length. + * + * @return string + */ + public function getSubstringExpression($value, $from, $length = null) + { + if ($length === null) { + return 'SUBSTRING(' . $value . ' FROM ' . $from . ')'; + } + + return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $length . ')'; + } + + /** + * Returns a SQL snippet to concatenate the given expressions. + * + * Accepts an arbitrary number of string parameters. Each parameter must contain an expression. + * + * @return string + */ + public function getConcatExpression() + { + return join(' || ', func_get_args()); + } + + /** + * Returns the SQL for a logical not. + * + * Example: + * + * $q = new Doctrine_Query(); + * $e = $q->expr; + * $q->select('*')->from('table') + * ->where($e->eq('id', $e->not('null')); + * + * + * @param string $expression + * + * @return string The logical expression. + */ + public function getNotExpression($expression) + { + return 'NOT(' . $expression . ')'; + } + + /** + * Returns the SQL that checks if an expression is null. + * + * @param string $expression The expression that should be compared to null. + * + * @return string The logical expression. + */ + public function getIsNullExpression($expression) + { + return $expression . ' IS NULL'; + } + + /** + * Returns the SQL that checks if an expression is not null. + * + * @param string $expression The expression that should be compared to null. + * + * @return string The logical expression. + */ + public function getIsNotNullExpression($expression) + { + return $expression . ' IS NOT NULL'; + } + + /** + * Returns the SQL that checks if an expression evaluates to a value between two values. + * + * The parameter $expression is checked if it is between $value1 and $value2. + * + * Note: There is a slight difference in the way BETWEEN works on some databases. + * http://www.w3schools.com/sql/sql_between.asp. If you want complete database + * independence you should avoid using between(). + * + * @param string $expression The value to compare to. + * @param string $value1 The lower value to compare with. + * @param string $value2 The higher value to compare with. + * + * @return string The logical expression. + */ + public function getBetweenExpression($expression, $value1, $value2) + { + return $expression . ' BETWEEN ' .$value1 . ' AND ' . $value2; + } + + /** + * Returns the SQL to get the arccosine of a value. + * + * @param string $value + * + * @return string + */ + public function getAcosExpression($value) + { + return 'ACOS(' . $value . ')'; + } + + /** + * Returns the SQL to get the sine of a value. + * + * @param string $value + * + * @return string + */ + public function getSinExpression($value) + { + return 'SIN(' . $value . ')'; + } + + /** + * Returns the SQL to get the PI value. + * + * @return string + */ + public function getPiExpression() + { + return 'PI()'; + } + + /** + * Returns the SQL to get the cosine of a value. + * + * @param string $value + * + * @return string + */ + public function getCosExpression($value) + { + return 'COS(' . $value . ')'; + } + + /** + * Returns the SQL to calculate the difference in days between the two passed dates. + * + * Computes diff = date1 - date2. + * + * @param string $date1 + * @param string $date2 + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateDiffExpression($date1, $date2) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to add the number of given seconds to a date. + * + * @param string $date + * @param integer $seconds + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateAddSecondsExpression($date, $seconds) + { + return $this->getDateArithmeticIntervalExpression($date, '+', $seconds, self::DATE_INTERVAL_UNIT_SECOND); + } + + /** + * Returns the SQL to subtract the number of given seconds from a date. + * + * @param string $date + * @param integer $seconds + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateSubSecondsExpression($date, $seconds) + { + return $this->getDateArithmeticIntervalExpression($date, '-', $seconds, self::DATE_INTERVAL_UNIT_SECOND); + } + + /** + * Returns the SQL to add the number of given minutes to a date. + * + * @param string $date + * @param integer $minutes + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateAddMinutesExpression($date, $minutes) + { + return $this->getDateArithmeticIntervalExpression($date, '+', $minutes, self::DATE_INTERVAL_UNIT_MINUTE); + } + + /** + * Returns the SQL to subtract the number of given minutes from a date. + * + * @param string $date + * @param integer $minutes + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateSubMinutesExpression($date, $minutes) + { + return $this->getDateArithmeticIntervalExpression($date, '-', $minutes, self::DATE_INTERVAL_UNIT_MINUTE); + } + + /** + * Returns the SQL to add the number of given hours to a date. + * + * @param string $date + * @param integer $hours + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateAddHourExpression($date, $hours) + { + return $this->getDateArithmeticIntervalExpression($date, '+', $hours, self::DATE_INTERVAL_UNIT_HOUR); + } + + /** + * Returns the SQL to subtract the number of given hours to a date. + * + * @param string $date + * @param integer $hours + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateSubHourExpression($date, $hours) + { + return $this->getDateArithmeticIntervalExpression($date, '-', $hours, self::DATE_INTERVAL_UNIT_HOUR); + } + + /** + * Returns the SQL to add the number of given days to a date. + * + * @param string $date + * @param integer $days + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateAddDaysExpression($date, $days) + { + return $this->getDateArithmeticIntervalExpression($date, '+', $days, self::DATE_INTERVAL_UNIT_DAY); + } + + /** + * Returns the SQL to subtract the number of given days to a date. + * + * @param string $date + * @param integer $days + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateSubDaysExpression($date, $days) + { + return $this->getDateArithmeticIntervalExpression($date, '-', $days, self::DATE_INTERVAL_UNIT_DAY); + } + + /** + * Returns the SQL to add the number of given weeks to a date. + * + * @param string $date + * @param integer $weeks + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateAddWeeksExpression($date, $weeks) + { + return $this->getDateArithmeticIntervalExpression($date, '+', $weeks, self::DATE_INTERVAL_UNIT_WEEK); + } + + /** + * Returns the SQL to subtract the number of given weeks from a date. + * + * @param string $date + * @param integer $weeks + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateSubWeeksExpression($date, $weeks) + { + return $this->getDateArithmeticIntervalExpression($date, '-', $weeks, self::DATE_INTERVAL_UNIT_WEEK); + } + + /** + * Returns the SQL to add the number of given months to a date. + * + * @param string $date + * @param integer $months + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateAddMonthExpression($date, $months) + { + return $this->getDateArithmeticIntervalExpression($date, '+', $months, self::DATE_INTERVAL_UNIT_MONTH); + } + + /** + * Returns the SQL to subtract the number of given months to a date. + * + * @param string $date + * @param integer $months + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateSubMonthExpression($date, $months) + { + return $this->getDateArithmeticIntervalExpression($date, '-', $months, self::DATE_INTERVAL_UNIT_MONTH); + } + + /** + * Returns the SQL to add the number of given quarters to a date. + * + * @param string $date + * @param integer $quarters + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateAddQuartersExpression($date, $quarters) + { + return $this->getDateArithmeticIntervalExpression($date, '+', $quarters, self::DATE_INTERVAL_UNIT_QUARTER); + } + + /** + * Returns the SQL to subtract the number of given quarters from a date. + * + * @param string $date + * @param integer $quarters + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateSubQuartersExpression($date, $quarters) + { + return $this->getDateArithmeticIntervalExpression($date, '-', $quarters, self::DATE_INTERVAL_UNIT_QUARTER); + } + + /** + * Returns the SQL to add the number of given years to a date. + * + * @param string $date + * @param integer $years + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateAddYearsExpression($date, $years) + { + return $this->getDateArithmeticIntervalExpression($date, '+', $years, self::DATE_INTERVAL_UNIT_YEAR); + } + + /** + * Returns the SQL to subtract the number of given years from a date. + * + * @param string $date + * @param integer $years + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateSubYearsExpression($date, $years) + { + return $this->getDateArithmeticIntervalExpression($date, '-', $years, self::DATE_INTERVAL_UNIT_YEAR); + } + + /** + * Returns the SQL for a date arithmetic expression. + * + * @param string $date The column or literal representing a date to perform the arithmetic operation on. + * @param string $operator The arithmetic operator (+ or -). + * @param integer $interval The interval that shall be calculated into the date. + * @param string $unit The unit of the interval that shall be calculated into the date. + * One of the DATE_INTERVAL_UNIT_* constants. + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL bit AND comparison expression. + * + * @param string $value1 + * @param string $value2 + * + * @return string + */ + public function getBitAndComparisonExpression($value1, $value2) + { + return '(' . $value1 . ' & ' . $value2 . ')'; + } + + /** + * Returns the SQL bit OR comparison expression. + * + * @param string $value1 + * @param string $value2 + * + * @return string + */ + public function getBitOrComparisonExpression($value1, $value2) + { + return '(' . $value1 . ' | ' . $value2 . ')'; + } + + /** + * Returns the FOR UPDATE expression. + * + * @return string + */ + public function getForUpdateSQL() + { + return 'FOR UPDATE'; + } + + /** + * Honors that some SQL vendors such as MsSql use table hints for locking instead of the ANSI SQL FOR UPDATE specification. + * + * @param string $fromClause The FROM clause to append the hint for the given lock mode to. + * @param integer|null $lockMode One of the Doctrine\DBAL\LockMode::* constants. If null is given, nothing will + * be appended to the FROM clause. + * + * @return string + */ + public function appendLockHint($fromClause, $lockMode) + { + return $fromClause; + } + + /** + * Returns the SQL snippet to append to any SELECT statement which locks rows in shared read lock. + * + * This defaults to the ANSI SQL "FOR UPDATE", which is an exclusive lock (Write). Some database + * vendors allow to lighten this constraint up to be a real read lock. + * + * @return string + */ + public function getReadLockSQL() + { + return $this->getForUpdateSQL(); + } + + /** + * Returns the SQL snippet to append to any SELECT statement which obtains an exclusive lock on the rows. + * + * The semantics of this lock mode should equal the SELECT .. FOR UPDATE of the ANSI SQL standard. + * + * @return string + */ + public function getWriteLockSQL() + { + return $this->getForUpdateSQL(); + } + + /** + * Returns the SQL snippet to drop an existing database. + * + * @param string $database The name of the database that should be dropped. + * + * @return string + */ + public function getDropDatabaseSQL($database) + { + return 'DROP DATABASE ' . $database; + } + + /** + * Returns the SQL snippet to drop an existing table. + * + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return string + * + * @throws \InvalidArgumentException + */ + public function getDropTableSQL($table) + { + $tableArg = $table; + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } elseif (!is_string($table)) { + throw new \InvalidArgumentException('getDropTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); + } + + if (null !== $this->_eventManager && $this->_eventManager->hasListeners(Events::onSchemaDropTable)) { + $eventArgs = new SchemaDropTableEventArgs($tableArg, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaDropTable, $eventArgs); + + if ($eventArgs->isDefaultPrevented()) { + return $eventArgs->getSql(); + } + } + + return 'DROP TABLE ' . $table; + } + + /** + * Returns the SQL to safely drop a temporary table WITHOUT implicitly committing an open transaction. + * + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return string + */ + public function getDropTemporaryTableSQL($table) + { + return $this->getDropTableSQL($table); + } + + /** + * Returns the SQL to drop an index from a table. + * + * @param \Doctrine\DBAL\Schema\Index|string $index + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return string + * + * @throws \InvalidArgumentException + */ + public function getDropIndexSQL($index, $table = null) + { + if ($index instanceof Index) { + $index = $index->getQuotedName($this); + } elseif (!is_string($index)) { + throw new \InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.'); + } + + return 'DROP INDEX ' . $index; + } + + /** + * Returns the SQL to drop a constraint. + * + * @param \Doctrine\DBAL\Schema\Constraint|string $constraint + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return string + */ + public function getDropConstraintSQL($constraint, $table) + { + if (! $constraint instanceof Constraint) { + $constraint = new Identifier($constraint); + } + + if (! $table instanceof Table) { + $table = new Identifier($table); + } + + $constraint = $constraint->getQuotedName($this); + $table = $table->getQuotedName($this); + + return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $constraint; + } + + /** + * Returns the SQL to drop a foreign key. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint|string $foreignKey + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return string + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + if (! $foreignKey instanceof ForeignKeyConstraint) { + $foreignKey = new Identifier($foreignKey); + } + + if (! $table instanceof Table) { + $table = new Identifier($table); + } + + $foreignKey = $foreignKey->getQuotedName($this); + $table = $table->getQuotedName($this); + + return 'ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $foreignKey; + } + + /** + * Returns the SQL statement(s) to create a table with the specified name, columns and constraints + * on this platform. + * + * @param \Doctrine\DBAL\Schema\Table $table + * @param integer $createFlags + * + * @return array The sequence of SQL statements. + * + * @throws \Doctrine\DBAL\DBALException + * @throws \InvalidArgumentException + */ + public function getCreateTableSQL(Table $table, $createFlags = self::CREATE_INDEXES) + { + if ( ! is_int($createFlags)) { + throw new \InvalidArgumentException("Second argument of AbstractPlatform::getCreateTableSQL() has to be integer."); + } + + if (count($table->getColumns()) === 0) { + throw DBALException::noColumnsSpecifiedForTable($table->getName()); + } + + $tableName = $table->getQuotedName($this); + $options = $table->getOptions(); + $options['uniqueConstraints'] = array(); + $options['indexes'] = array(); + $options['primary'] = array(); + + if (($createFlags&self::CREATE_INDEXES) > 0) { + foreach ($table->getIndexes() as $index) { + /* @var $index Index */ + if ($index->isPrimary()) { + $options['primary'] = $index->getQuotedColumns($this); + $options['primary_index'] = $index; + } else { + $options['indexes'][$index->getQuotedName($this)] = $index; + } + } + } + + $columnSql = array(); + $columns = array(); + + foreach ($table->getColumns() as $column) { + /* @var \Doctrine\DBAL\Schema\Column $column */ + + if (null !== $this->_eventManager && $this->_eventManager->hasListeners(Events::onSchemaCreateTableColumn)) { + $eventArgs = new SchemaCreateTableColumnEventArgs($column, $table, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaCreateTableColumn, $eventArgs); + + $columnSql = array_merge($columnSql, $eventArgs->getSql()); + + if ($eventArgs->isDefaultPrevented()) { + continue; + } + } + + $columnData = $column->toArray(); + $columnData['name'] = $column->getQuotedName($this); + $columnData['version'] = $column->hasPlatformOption("version") ? $column->getPlatformOption('version') : false; + $columnData['comment'] = $this->getColumnComment($column); + + if (strtolower($columnData['type']) == "string" && $columnData['length'] === null) { + $columnData['length'] = 255; + } + + if (in_array($column->getName(), $options['primary'])) { + $columnData['primary'] = true; + } + + $columns[$columnData['name']] = $columnData; + } + + if (($createFlags&self::CREATE_FOREIGNKEYS) > 0) { + $options['foreignKeys'] = array(); + foreach ($table->getForeignKeys() as $fkConstraint) { + $options['foreignKeys'][] = $fkConstraint; + } + } + + if (null !== $this->_eventManager && $this->_eventManager->hasListeners(Events::onSchemaCreateTable)) { + $eventArgs = new SchemaCreateTableEventArgs($table, $columns, $options, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaCreateTable, $eventArgs); + + if ($eventArgs->isDefaultPrevented()) { + return array_merge($eventArgs->getSql(), $columnSql); + } + } + + $sql = $this->_getCreateTableSQL($tableName, $columns, $options); + if ($this->supportsCommentOnStatement()) { + foreach ($table->getColumns() as $column) { + $comment = $this->getColumnComment($column); + + if (null !== $comment && '' !== $comment) { + $sql[] = $this->getCommentOnColumnSQL($tableName, $column->getQuotedName($this), $comment); + } + } + } + + return array_merge($sql, $columnSql); + } + + /** + * @param string $tableName + * @param string $columnName + * @param string $comment + * + * @return string + */ + public function getCommentOnColumnSQL($tableName, $columnName, $comment) + { + $tableName = new Identifier($tableName); + $columnName = new Identifier($columnName); + $comment = $this->quoteStringLiteral($comment); + + return "COMMENT ON COLUMN " . $tableName->getQuotedName($this) . "." . $columnName->getQuotedName($this) . + " IS " . $comment; + } + + /** + * Returns the SQL used to create a table. + * + * @param string $tableName + * @param array $columns + * @param array $options + * + * @return array + */ + protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) + { + $columnListSql = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + foreach ($options['uniqueConstraints'] as $name => $definition) { + $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition); + } + } + + if (isset($options['primary']) && ! empty($options['primary'])) { + $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')'; + } + + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach ($options['indexes'] as $index => $definition) { + $columnListSql .= ', ' . $this->getIndexDeclarationSQL($index, $definition); + } + } + + $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql; + + $check = $this->getCheckDeclarationSQL($columns); + if ( ! empty($check)) { + $query .= ', ' . $check; + } + $query .= ')'; + + $sql[] = $query; + + if (isset($options['foreignKeys'])) { + foreach ((array) $options['foreignKeys'] as $definition) { + $sql[] = $this->getCreateForeignKeySQL($definition, $tableName); + } + } + + return $sql; + } + + /** + * @return string + */ + public function getCreateTemporaryTableSnippetSQL() + { + return "CREATE TEMPORARY TABLE"; + } + + /** + * Returns the SQL to create a sequence on this platform. + * + * @param \Doctrine\DBAL\Schema\Sequence $sequence + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getCreateSequenceSQL(Sequence $sequence) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to change a sequence on this platform. + * + * @param \Doctrine\DBAL\Schema\Sequence $sequence + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getAlterSequenceSQL(Sequence $sequence) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to create a constraint on a table on this platform. + * + * @param \Doctrine\DBAL\Schema\Constraint $constraint + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return string + * + * @throws \InvalidArgumentException + */ + public function getCreateConstraintSQL(Constraint $constraint, $table) + { + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $constraint->getQuotedName($this); + + $columnList = '('. implode(', ', $constraint->getQuotedColumns($this)) . ')'; + + $referencesClause = ''; + if ($constraint instanceof Index) { + if ($constraint->isPrimary()) { + $query .= ' PRIMARY KEY'; + } elseif ($constraint->isUnique()) { + $query .= ' UNIQUE'; + } else { + throw new \InvalidArgumentException( + 'Can only create primary or unique constraints, no common indexes with getCreateConstraintSQL().' + ); + } + } elseif ($constraint instanceof ForeignKeyConstraint) { + $query .= ' FOREIGN KEY'; + + $referencesClause = ' REFERENCES ' . $constraint->getQuotedForeignTableName($this) . + ' (' . implode(', ', $constraint->getQuotedForeignColumns($this)) . ')'; + } + $query .= ' '.$columnList.$referencesClause; + + return $query; + } + + /** + * Returns the SQL to create an index on a table on this platform. + * + * @param \Doctrine\DBAL\Schema\Index $index + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table on which the index is to be created. + * + * @return string + * + * @throws \InvalidArgumentException + */ + public function getCreateIndexSQL(Index $index, $table) + { + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + $name = $index->getQuotedName($this); + $columns = $index->getQuotedColumns($this); + + if (count($columns) == 0) { + throw new \InvalidArgumentException("Incomplete definition. 'columns' required."); + } + + if ($index->isPrimary()) { + return $this->getCreatePrimaryKeySQL($index, $table); + } + + $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table; + $query .= ' (' . $this->getIndexFieldDeclarationListSQL($columns) . ')' . $this->getPartialIndexSQL($index); + + return $query; + } + + /** + * Adds condition for partial index. + * + * @param \Doctrine\DBAL\Schema\Index $index + * + * @return string + */ + protected function getPartialIndexSQL(Index $index) + { + if ($this->supportsPartialIndexes() && $index->hasOption('where')) { + return ' WHERE ' . $index->getOption('where'); + } + + return ''; + } + + /** + * Adds additional flags for index generation. + * + * @param \Doctrine\DBAL\Schema\Index $index + * + * @return string + */ + protected function getCreateIndexSQLFlags(Index $index) + { + return $index->isUnique() ? 'UNIQUE ' : ''; + } + + /** + * Returns the SQL to create an unnamed primary key constraint. + * + * @param \Doctrine\DBAL\Schema\Index $index + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return string + */ + public function getCreatePrimaryKeySQL(Index $index, $table) + { + return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY (' . $this->getIndexFieldDeclarationListSQL($index->getQuotedColumns($this)) . ')'; + } + + /** + * Returns the SQL to create a named schema. + * + * @param string $schemaName + * + * @return string + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getCreateSchemaSQL($schemaName) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Quotes a string so that it can be safely used as a table or column name, + * even if it is a reserved word of the platform. This also detects identifier + * chains separated by dot and quotes them independently. + * + * NOTE: Just because you CAN use quoted identifiers doesn't mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. + * + * @param string $str The identifier name to be quoted. + * + * @return string The quoted identifier string. + */ + public function quoteIdentifier($str) + { + if (strpos($str, ".") !== false) { + $parts = array_map(array($this, "quoteSingleIdentifier"), explode(".", $str)); + + return implode(".", $parts); + } + + return $this->quoteSingleIdentifier($str); + } + + /** + * Quotes a single identifier (no dot chain separation). + * + * @param string $str The identifier name to be quoted. + * + * @return string The quoted identifier string. + */ + public function quoteSingleIdentifier($str) + { + $c = $this->getIdentifierQuoteCharacter(); + + return $c . str_replace($c, $c.$c, $str) . $c; + } + + /** + * Returns the SQL to create a new foreign key. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey The foreign key constraint. + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table on which the foreign key is to be created. + * + * @return string + */ + public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) + { + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + $query = 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSQL($foreignKey); + + return $query; + } + + /** + * Gets the SQL statements for altering an existing table. + * + * This method returns an array of SQL statements, since some platforms need several statements. + * + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getAlterTableSQL(TableDiff $diff) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * @param array $columnSql + * + * @return boolean + */ + protected function onSchemaAlterTableAddColumn(Column $column, TableDiff $diff, &$columnSql) + { + if (null === $this->_eventManager) { + return false; + } + + if ( ! $this->_eventManager->hasListeners(Events::onSchemaAlterTableAddColumn)) { + return false; + } + + $eventArgs = new SchemaAlterTableAddColumnEventArgs($column, $diff, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableAddColumn, $eventArgs); + + $columnSql = array_merge($columnSql, $eventArgs->getSql()); + + return $eventArgs->isDefaultPrevented(); + } + + /** + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * @param array $columnSql + * + * @return boolean + */ + protected function onSchemaAlterTableRemoveColumn(Column $column, TableDiff $diff, &$columnSql) + { + if (null === $this->_eventManager) { + return false; + } + + if ( ! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRemoveColumn)) { + return false; + } + + $eventArgs = new SchemaAlterTableRemoveColumnEventArgs($column, $diff, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRemoveColumn, $eventArgs); + + $columnSql = array_merge($columnSql, $eventArgs->getSql()); + + return $eventArgs->isDefaultPrevented(); + } + + /** + * @param \Doctrine\DBAL\Schema\ColumnDiff $columnDiff + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * @param array $columnSql + * + * @return boolean + */ + protected function onSchemaAlterTableChangeColumn(ColumnDiff $columnDiff, TableDiff $diff, &$columnSql) + { + if (null === $this->_eventManager) { + return false; + } + + if ( ! $this->_eventManager->hasListeners(Events::onSchemaAlterTableChangeColumn)) { + return false; + } + + $eventArgs = new SchemaAlterTableChangeColumnEventArgs($columnDiff, $diff, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableChangeColumn, $eventArgs); + + $columnSql = array_merge($columnSql, $eventArgs->getSql()); + + return $eventArgs->isDefaultPrevented(); + } + + /** + * @param string $oldColumnName + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * @param array $columnSql + * + * @return boolean + */ + protected function onSchemaAlterTableRenameColumn($oldColumnName, Column $column, TableDiff $diff, &$columnSql) + { + if (null === $this->_eventManager) { + return false; + } + + if ( ! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRenameColumn)) { + return false; + } + + $eventArgs = new SchemaAlterTableRenameColumnEventArgs($oldColumnName, $column, $diff, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRenameColumn, $eventArgs); + + $columnSql = array_merge($columnSql, $eventArgs->getSql()); + + return $eventArgs->isDefaultPrevented(); + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * @param array $sql + * + * @return boolean + */ + protected function onSchemaAlterTable(TableDiff $diff, &$sql) + { + if (null === $this->_eventManager) { + return false; + } + + if ( ! $this->_eventManager->hasListeners(Events::onSchemaAlterTable)) { + return false; + } + + $eventArgs = new SchemaAlterTableEventArgs($diff, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaAlterTable, $eventArgs); + + $sql = array_merge($sql, $eventArgs->getSql()); + + return $eventArgs->isDefaultPrevented(); + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array + */ + protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) + { + $tableName = $diff->getName($this)->getQuotedName($this); + + $sql = array(); + if ($this->supportsForeignKeyConstraints()) { + foreach ($diff->removedForeignKeys as $foreignKey) { + $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName); + } + foreach ($diff->changedForeignKeys as $foreignKey) { + $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName); + } + } + + foreach ($diff->removedIndexes as $index) { + $sql[] = $this->getDropIndexSQL($index, $tableName); + } + foreach ($diff->changedIndexes as $index) { + $sql[] = $this->getDropIndexSQL($index, $tableName); + } + + return $sql; + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array + */ + protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) + { + $tableName = (false !== $diff->newName) + ? $diff->getNewName()->getQuotedName($this) + : $diff->getName($this)->getQuotedName($this); + + $sql = array(); + + if ($this->supportsForeignKeyConstraints()) { + foreach ($diff->addedForeignKeys as $foreignKey) { + $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName); + } + + foreach ($diff->changedForeignKeys as $foreignKey) { + $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName); + } + } + + foreach ($diff->addedIndexes as $index) { + $sql[] = $this->getCreateIndexSQL($index, $tableName); + } + + foreach ($diff->changedIndexes as $index) { + $sql[] = $this->getCreateIndexSQL($index, $tableName); + } + + foreach ($diff->renamedIndexes as $oldIndexName => $index) { + $oldIndexName = new Identifier($oldIndexName); + $sql = array_merge( + $sql, + $this->getRenameIndexSQL($oldIndexName->getQuotedName($this), $index, $tableName) + ); + } + + return $sql; + } + + /** + * Returns the SQL for renaming an index on a table. + * + * @param string $oldIndexName The name of the index to rename from. + * @param \Doctrine\DBAL\Schema\Index $index The definition of the index to rename to. + * @param string $tableName The table to rename the given index on. + * + * @return array The sequence of SQL statements for renaming the given index. + */ + protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) + { + return array( + $this->getDropIndexSQL($oldIndexName, $tableName), + $this->getCreateIndexSQL($index, $tableName) + ); + } + + /** + * Common code for alter table statement generation that updates the changed Index and Foreign Key definitions. + * + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array + */ + protected function _getAlterTableIndexForeignKeySQL(TableDiff $diff) + { + return array_merge($this->getPreAlterTableIndexForeignKeySQL($diff), $this->getPostAlterTableIndexForeignKeySQL($diff)); + } + + /** + * Gets declaration of a number of fields in bulk. + * + * @param array $fields A multidimensional associative array. + * The first dimension determines the field name, while the second + * dimension is keyed with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * charset + * Text value with the default CHARACTER SET for this field. + * collation + * Text value with the default COLLATION for this field. + * unique + * unique constraint + * + * @return string + */ + public function getColumnDeclarationListSQL(array $fields) + { + $queryFields = array(); + + foreach ($fields as $fieldName => $field) { + $queryFields[] = $this->getColumnDeclarationSQL($fieldName, $field); + } + + return implode(', ', $queryFields); + } + + /** + * Obtains DBMS specific SQL code portion needed to declare a generic type + * field to be used in statements like CREATE TABLE. + * + * @param string $name The name the field to be declared. + * @param array $field An associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * charset + * Text value with the default CHARACTER SET for this field. + * collation + * Text value with the default COLLATION for this field. + * unique + * unique constraint + * check + * column check constraint + * columnDefinition + * a string that defines the complete column + * + * @return string DBMS specific SQL code portion that should be used to declare the column. + */ + public function getColumnDeclarationSQL($name, array $field) + { + if (isset($field['columnDefinition'])) { + $columnDef = $this->getCustomTypeDeclarationSQL($field); + } else { + $default = $this->getDefaultValueDeclarationSQL($field); + + $charset = (isset($field['charset']) && $field['charset']) ? + ' ' . $this->getColumnCharsetDeclarationSQL($field['charset']) : ''; + + $collation = (isset($field['collation']) && $field['collation']) ? + ' ' . $this->getColumnCollationDeclarationSQL($field['collation']) : ''; + + $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : ''; + + $unique = (isset($field['unique']) && $field['unique']) ? + ' ' . $this->getUniqueFieldDeclarationSQL() : ''; + + $check = (isset($field['check']) && $field['check']) ? + ' ' . $field['check'] : ''; + + $typeDecl = $field['type']->getSqlDeclaration($field, $this); + $columnDef = $typeDecl . $charset . $default . $notnull . $unique . $check . $collation; + } + + if ($this->supportsInlineColumnComments() && isset($field['comment']) && $field['comment'] !== '') { + $columnDef .= " COMMENT " . $this->quoteStringLiteral($field['comment']); + } + + return $name . ' ' . $columnDef; + } + + /** + * Returns the SQL snippet that declares a floating point column of arbitrary precision. + * + * @param array $columnDef + * + * @return string + */ + public function getDecimalTypeDeclarationSQL(array $columnDef) + { + $columnDef['precision'] = ( ! isset($columnDef['precision']) || empty($columnDef['precision'])) + ? 10 : $columnDef['precision']; + $columnDef['scale'] = ( ! isset($columnDef['scale']) || empty($columnDef['scale'])) + ? 0 : $columnDef['scale']; + + return 'NUMERIC(' . $columnDef['precision'] . ', ' . $columnDef['scale'] . ')'; + } + + /** + * Obtains DBMS specific SQL code portion needed to set a default value + * declaration to be used in statements like CREATE TABLE. + * + * @param array $field The field definition array. + * + * @return string DBMS specific SQL code portion needed to set a default value. + */ + public function getDefaultValueDeclarationSQL($field) + { + $default = empty($field['notnull']) ? ' DEFAULT NULL' : ''; + + if (isset($field['default'])) { + $default = " DEFAULT '".$field['default']."'"; + if (isset($field['type'])) { + if (in_array((string) $field['type'], array("Integer", "BigInt", "SmallInt"))) { + $default = " DEFAULT ".$field['default']; + } elseif (in_array((string) $field['type'], array('DateTime', 'DateTimeTz')) && $field['default'] == $this->getCurrentTimestampSQL()) { + $default = " DEFAULT ".$this->getCurrentTimestampSQL(); + } elseif ((string) $field['type'] == 'Time' && $field['default'] == $this->getCurrentTimeSQL()) { + $default = " DEFAULT ".$this->getCurrentTimeSQL(); + } elseif ((string) $field['type'] == 'Date' && $field['default'] == $this->getCurrentDateSQL()) { + $default = " DEFAULT ".$this->getCurrentDateSQL(); + } elseif ((string) $field['type'] == 'Boolean') { + $default = " DEFAULT '" . $this->convertBooleans($field['default']) . "'"; + } + } + } + + return $default; + } + + /** + * Obtains DBMS specific SQL code portion needed to set a CHECK constraint + * declaration to be used in statements like CREATE TABLE. + * + * @param array $definition The check definition. + * + * @return string DBMS specific SQL code portion needed to set a CHECK constraint. + */ + public function getCheckDeclarationSQL(array $definition) + { + $constraints = array(); + foreach ($definition as $field => $def) { + if (is_string($def)) { + $constraints[] = 'CHECK (' . $def . ')'; + } else { + if (isset($def['min'])) { + $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')'; + } + + if (isset($def['max'])) { + $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')'; + } + } + } + + return implode(', ', $constraints); + } + + /** + * Obtains DBMS specific SQL code portion needed to set a unique + * constraint declaration to be used in statements like CREATE TABLE. + * + * @param string $name The name of the unique constraint. + * @param \Doctrine\DBAL\Schema\Index $index The index definition. + * + * @return string DBMS specific SQL code portion needed to set a constraint. + * + * @throws \InvalidArgumentException + */ + public function getUniqueConstraintDeclarationSQL($name, Index $index) + { + $columns = $index->getQuotedColumns($this); + $name = new Identifier($name); + + if (count($columns) === 0) { + throw new \InvalidArgumentException("Incomplete definition. 'columns' required."); + } + + return 'CONSTRAINT ' . $name->getQuotedName($this) . ' UNIQUE (' + . $this->getIndexFieldDeclarationListSQL($columns) + . ')' . $this->getPartialIndexSQL($index); + } + + /** + * Obtains DBMS specific SQL code portion needed to set an index + * declaration to be used in statements like CREATE TABLE. + * + * @param string $name The name of the index. + * @param \Doctrine\DBAL\Schema\Index $index The index definition. + * + * @return string DBMS specific SQL code portion needed to set an index. + * + * @throws \InvalidArgumentException + */ + public function getIndexDeclarationSQL($name, Index $index) + { + $columns = $index->getQuotedColumns($this); + $name = new Identifier($name); + + if (count($columns) === 0) { + throw new \InvalidArgumentException("Incomplete definition. 'columns' required."); + } + + return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name->getQuotedName($this) . ' (' + . $this->getIndexFieldDeclarationListSQL($columns) + . ')' . $this->getPartialIndexSQL($index); + } + + /** + * Obtains SQL code portion needed to create a custom column, + * e.g. when a field has the "columnDefinition" keyword. + * Only "AUTOINCREMENT" and "PRIMARY KEY" are added if appropriate. + * + * @param array $columnDef + * + * @return string + */ + public function getCustomTypeDeclarationSQL(array $columnDef) + { + return $columnDef['columnDefinition']; + } + + /** + * Obtains DBMS specific SQL code portion needed to set an index + * declaration to be used in statements like CREATE TABLE. + * + * @param array $fields + * + * @return string + */ + public function getIndexFieldDeclarationListSQL(array $fields) + { + $ret = array(); + + foreach ($fields as $field => $definition) { + if (is_array($definition)) { + $ret[] = $field; + } else { + $ret[] = $definition; + } + } + + return implode(', ', $ret); + } + + /** + * Returns the required SQL string that fits between CREATE ... TABLE + * to create the table as a temporary table. + * + * Should be overridden in driver classes to return the correct string for the + * specific database type. + * + * The default is to return the string "TEMPORARY" - this will result in a + * SQL error for any database that does not support temporary tables, or that + * requires a different SQL command from "CREATE TEMPORARY TABLE". + * + * @return string The string required to be placed between "CREATE" and "TABLE" + * to generate a temporary table, if possible. + */ + public function getTemporaryTableSQL() + { + return 'TEMPORARY'; + } + + /** + * Some vendors require temporary table names to be qualified specially. + * + * @param string $tableName + * + * @return string + */ + public function getTemporaryTableName($tableName) + { + return $tableName; + } + + /** + * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey + * + * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint + * of a field declaration. + */ + public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) + { + $sql = $this->getForeignKeyBaseDeclarationSQL($foreignKey); + $sql .= $this->getAdvancedForeignKeyOptionsSQL($foreignKey); + + return $sql; + } + + /** + * Returns the FOREIGN KEY query section dealing with non-standard options + * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey The foreign key definition. + * + * @return string + */ + public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) + { + $query = ''; + if ($this->supportsForeignKeyOnUpdate() && $foreignKey->hasOption('onUpdate')) { + $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onUpdate')); + } + if ($foreignKey->hasOption('onDelete')) { + $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete')); + } + + return $query; + } + + /** + * Returns the given referential action in uppercase if valid, otherwise throws an exception. + * + * @param string $action The foreign key referential action. + * + * @return string + * + * @throws \InvalidArgumentException if unknown referential action given + */ + public function getForeignKeyReferentialActionSQL($action) + { + $upper = strtoupper($action); + switch ($upper) { + case 'CASCADE': + case 'SET NULL': + case 'NO ACTION': + case 'RESTRICT': + case 'SET DEFAULT': + return $upper; + default: + throw new \InvalidArgumentException('Invalid foreign key action: ' . $upper); + } + } + + /** + * Obtains DBMS specific SQL code portion needed to set the FOREIGN KEY constraint + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey + * + * @return string + * + * @throws \InvalidArgumentException + */ + public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey) + { + $sql = ''; + if (strlen($foreignKey->getName())) { + $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' '; + } + $sql .= 'FOREIGN KEY ('; + + if (count($foreignKey->getLocalColumns()) === 0) { + throw new \InvalidArgumentException("Incomplete definition. 'local' required."); + } + if (count($foreignKey->getForeignColumns()) === 0) { + throw new \InvalidArgumentException("Incomplete definition. 'foreign' required."); + } + if (strlen($foreignKey->getForeignTableName()) === 0) { + throw new \InvalidArgumentException("Incomplete definition. 'foreignTable' required."); + } + + $sql .= implode(', ', $foreignKey->getQuotedLocalColumns($this)) + . ') REFERENCES ' + . $foreignKey->getQuotedForeignTableName($this) . ' (' + . implode(', ', $foreignKey->getQuotedForeignColumns($this)) . ')'; + + return $sql; + } + + /** + * Obtains DBMS specific SQL code portion needed to set the UNIQUE constraint + * of a field declaration to be used in statements like CREATE TABLE. + * + * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint + * of a field declaration. + */ + public function getUniqueFieldDeclarationSQL() + { + return 'UNIQUE'; + } + + /** + * Obtains DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $charset The name of the charset. + * + * @return string DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration. + */ + public function getColumnCharsetDeclarationSQL($charset) + { + return ''; + } + + /** + * Obtains DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $collation The name of the collation. + * + * @return string DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration. + */ + public function getColumnCollationDeclarationSQL($collation) + { + return $this->supportsColumnCollation() ? 'COLLATE ' . $collation : ''; + } + + /** + * Whether the platform prefers sequences for ID generation. + * Subclasses should override this method to return TRUE if they prefer sequences. + * + * @return boolean + */ + public function prefersSequences() + { + return false; + } + + /** + * Whether the platform prefers identity columns (eg. autoincrement) for ID generation. + * Subclasses should override this method to return TRUE if they prefer identity columns. + * + * @return boolean + */ + public function prefersIdentityColumns() + { + return false; + } + + /** + * Some platforms need the boolean values to be converted. + * + * The default conversion in this implementation converts to integers (false => 0, true => 1). + * + * Note: if the input is not a boolean the original input might be returned. + * + * There are two contexts when converting booleans: Literals and Prepared Statements. + * This method should handle the literal case + * + * @param mixed $item A boolean or an array of them. + * + * @return mixed A boolean database value or an array of them. + */ + public function convertBooleans($item) + { + if (is_array($item)) { + foreach ($item as $k => $value) { + if (is_bool($value)) { + $item[$k] = (int) $value; + } + } + } elseif (is_bool($item)) { + $item = (int) $item; + } + + return $item; + } + + /** + * Some platforms have boolean literals that needs to be correctly converted + * + * The default conversion tries to convert value into bool "(bool)$item" + * + * @param mixed $item + * + * @return bool|null + */ + public function convertFromBoolean($item) + { + return null === $item ? null: (bool) $item ; + } + + /** + * This method should handle the prepared statements case. When there is no + * distinction, it's OK to use the same method. + * + * Note: if the input is not a boolean the original input might be returned. + * + * @param mixed $item A boolean or an array of them. + * + * @return mixed A boolean database value or an array of them. + */ + public function convertBooleansToDatabaseValue($item) + { + return $this->convertBooleans($item); + } + + /** + * Returns the SQL specific for the platform to get the current date. + * + * @return string + */ + public function getCurrentDateSQL() + { + return 'CURRENT_DATE'; + } + + /** + * Returns the SQL specific for the platform to get the current time. + * + * @return string + */ + public function getCurrentTimeSQL() + { + return 'CURRENT_TIME'; + } + + /** + * Returns the SQL specific for the platform to get the current timestamp + * + * @return string + */ + public function getCurrentTimestampSQL() + { + return 'CURRENT_TIMESTAMP'; + } + + /** + * Returns the SQL for a given transaction isolation level Connection constant. + * + * @param integer $level + * + * @return string + * + * @throws \InvalidArgumentException + */ + protected function _getTransactionIsolationLevelSQL($level) + { + switch ($level) { + case Connection::TRANSACTION_READ_UNCOMMITTED: + return 'READ UNCOMMITTED'; + case Connection::TRANSACTION_READ_COMMITTED: + return 'READ COMMITTED'; + case Connection::TRANSACTION_REPEATABLE_READ: + return 'REPEATABLE READ'; + case Connection::TRANSACTION_SERIALIZABLE: + return 'SERIALIZABLE'; + default: + throw new \InvalidArgumentException('Invalid isolation level:' . $level); + } + } + + /** + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListDatabasesSQL() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL statement for retrieving the namespaces defined in the database. + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListNamespacesSQL() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param string $database + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListSequencesSQL($database) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param string $table + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListTableConstraintsSQL($table) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param string $table + * @param string|null $database + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListTableColumnsSQL($table, $database = null) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListTablesSQL() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListUsersSQL() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to list all views of a database or user. + * + * @param string $database + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListViewsSQL($database) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the list of indexes for the current database. + * + * The current database parameter is optional but will always be passed + * when using the SchemaManager API and is the database the given table is in. + * + * Attention: Some platforms only support currentDatabase when they + * are connected with that database. Cross-database information schema + * requests may be impossible. + * + * @param string $table + * @param string $currentDatabase + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param string $table + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListTableForeignKeysSQL($table) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param string $name + * @param string $sql + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getCreateViewSQL($name, $sql) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param string $name + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDropViewSQL($name) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL snippet to drop an existing sequence. + * + * @param Sequence|string $sequence + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDropSequenceSQL($sequence) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param string $sequenceName + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getSequenceNextValSQL($sequenceName) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to create a new database. + * + * @param string $database The name of the database that should be created. + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getCreateDatabaseSQL($database) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to set the transaction isolation level. + * + * @param integer $level + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getSetTransactionIsolationSQL($level) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Obtains DBMS specific SQL to be used to create datetime fields in + * statements like CREATE TABLE. + * + * @param array $fieldDeclaration + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Obtains DBMS specific SQL to be used to create datetime with timezone offset fields. + * + * @param array $fieldDeclaration + * + * @return string + */ + public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) + { + return $this->getDateTimeTypeDeclarationSQL($fieldDeclaration); + } + + + /** + * Obtains DBMS specific SQL to be used to create date fields in statements + * like CREATE TABLE. + * + * @param array $fieldDeclaration + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Obtains DBMS specific SQL to be used to create time fields in statements + * like CREATE TABLE. + * + * @param array $fieldDeclaration + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param array $fieldDeclaration + * + * @return string + */ + public function getFloatDeclarationSQL(array $fieldDeclaration) + { + return 'DOUBLE PRECISION'; + } + + /** + * Gets the default transaction isolation level of the platform. + * + * @return integer The default isolation level. + * + * @see Doctrine\DBAL\Connection\TRANSACTION_* constants. + */ + public function getDefaultTransactionIsolationLevel() + { + return Connection::TRANSACTION_READ_COMMITTED; + } + + /* supports*() methods */ + + /** + * Whether the platform supports sequences. + * + * @return boolean + */ + public function supportsSequences() + { + return false; + } + + /** + * Whether the platform supports identity columns. + * + * Identity columns are columns that receive an auto-generated value from the + * database on insert of a row. + * + * @return boolean + */ + public function supportsIdentityColumns() + { + return false; + } + + /** + * Whether the platform emulates identity columns through sequences. + * + * Some platforms that do not support identity columns natively + * but support sequences can emulate identity columns by using + * sequences. + * + * @return boolean + */ + public function usesSequenceEmulatedIdentityColumns() + { + return false; + } + + /** + * Returns the name of the sequence for a particular identity column in a particular table. + * + * @param string $tableName The name of the table to return the sequence name for. + * @param string $columnName The name of the identity column in the table to return the sequence name for. + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + * + * @see usesSequenceEmulatedIdentityColumns + */ + public function getIdentitySequenceName($tableName, $columnName) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Whether the platform supports indexes. + * + * @return boolean + */ + public function supportsIndexes() + { + return true; + } + + /** + * Whether the platform supports partial indexes. + * + * @return boolean + */ + public function supportsPartialIndexes() + { + return false; + } + + /** + * Whether the platform supports altering tables. + * + * @return boolean + */ + public function supportsAlterTable() + { + return true; + } + + /** + * Whether the platform supports transactions. + * + * @return boolean + */ + public function supportsTransactions() + { + return true; + } + + /** + * Whether the platform supports savepoints. + * + * @return boolean + */ + public function supportsSavepoints() + { + return true; + } + + /** + * Whether the platform supports releasing savepoints. + * + * @return boolean + */ + public function supportsReleaseSavepoints() + { + return $this->supportsSavepoints(); + } + + /** + * Whether the platform supports primary key constraints. + * + * @return boolean + */ + public function supportsPrimaryConstraints() + { + return true; + } + + /** + * Whether the platform supports foreign key constraints. + * + * @return boolean + */ + public function supportsForeignKeyConstraints() + { + return true; + } + + /** + * Whether this platform supports onUpdate in foreign key constraints. + * + * @return boolean + */ + public function supportsForeignKeyOnUpdate() + { + return ($this->supportsForeignKeyConstraints() && true); + } + + /** + * Whether the platform supports database schemas. + * + * @return boolean + */ + public function supportsSchemas() + { + return false; + } + + /** + * Whether this platform can emulate schemas. + * + * Platforms that either support or emulate schemas don't automatically + * filter a schema for the namespaced elements in {@link + * AbstractManager#createSchema}. + * + * @return boolean + */ + public function canEmulateSchemas() + { + return false; + } + + /** + * Returns the default schema name. + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDefaultSchemaName() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Whether this platform supports create database. + * + * Some databases don't allow to create and drop databases at all or only with certain tools. + * + * @return boolean + */ + public function supportsCreateDropDatabase() + { + return true; + } + + /** + * Whether the platform supports getting the affected rows of a recent update/delete type query. + * + * @return boolean + */ + public function supportsGettingAffectedRows() + { + return true; + } + + /** + * Whether this platform support to add inline column comments as postfix. + * + * @return boolean + */ + public function supportsInlineColumnComments() + { + return false; + } + + /** + * Whether this platform support the proprietary syntax "COMMENT ON asset". + * + * @return boolean + */ + public function supportsCommentOnStatement() + { + return false; + } + + /** + * Does this platform have native guid type. + * + * @return boolean + */ + public function hasNativeGuidType() + { + return false; + } + + /** + * Does this platform have native JSON type. + * + * @return boolean + */ + public function hasNativeJsonType() + { + return false; + } + + /** + * @deprecated + * @todo Remove in 3.0 + */ + public function getIdentityColumnNullInsertSQL() + { + return ""; + } + + /** + * Whether this platform supports views. + * + * @return boolean + */ + public function supportsViews() + { + return true; + } + + /** + * Does this platform support column collation? + * + * @return boolean + */ + public function supportsColumnCollation() + { + return false; + } + + /** + * Gets the format string, as accepted by the date() function, that describes + * the format of a stored datetime value of this platform. + * + * @return string The format string. + */ + public function getDateTimeFormatString() + { + return 'Y-m-d H:i:s'; + } + + /** + * Gets the format string, as accepted by the date() function, that describes + * the format of a stored datetime with timezone value of this platform. + * + * @return string The format string. + */ + public function getDateTimeTzFormatString() + { + return 'Y-m-d H:i:s'; + } + + /** + * Gets the format string, as accepted by the date() function, that describes + * the format of a stored date value of this platform. + * + * @return string The format string. + */ + public function getDateFormatString() + { + return 'Y-m-d'; + } + + /** + * Gets the format string, as accepted by the date() function, that describes + * the format of a stored time value of this platform. + * + * @return string The format string. + */ + public function getTimeFormatString() + { + return 'H:i:s'; + } + + /** + * Adds an driver-specific LIMIT clause to the query. + * + * @param string $query + * @param integer|null $limit + * @param integer|null $offset + * + * @return string + * + * @throws DBALException + */ + final public function modifyLimitQuery($query, $limit, $offset = null) + { + if ($limit !== null) { + $limit = (int) $limit; + } + + if ($offset !== null) { + $offset = (int) $offset; + + if ($offset < 0) { + throw new DBALException("LIMIT argument offset=$offset is not valid"); + } + if ($offset > 0 && ! $this->supportsLimitOffset()) { + throw new DBALException(sprintf("Platform %s does not support offset values in limit queries.", $this->getName())); + } + } + + return $this->doModifyLimitQuery($query, $limit, $offset); + } + + /** + * Adds an driver-specific LIMIT clause to the query. + * + * @param string $query + * @param integer|null $limit + * @param integer|null $offset + * + * @return string + */ + protected function doModifyLimitQuery($query, $limit, $offset) + { + if ($limit !== null) { + $query .= ' LIMIT ' . $limit; + } + + if ($offset !== null) { + $query .= ' OFFSET ' . $offset; + } + + return $query; + } + + /** + * Whether the database platform support offsets in modify limit clauses. + * + * @return boolean + */ + public function supportsLimitOffset() + { + return true; + } + + /** + * Gets the character casing of a column in an SQL result set of this platform. + * + * @param string $column The column name for which to get the correct character casing. + * + * @return string The column name in the character casing used in SQL result sets. + */ + public function getSQLResultCasing($column) + { + return $column; + } + + /** + * Makes any fixes to a name of a schema element (table, sequence, ...) that are required + * by restrictions of the platform, like a maximum length. + * + * @param string $schemaElementName + * + * @return string + */ + public function fixSchemaElementName($schemaElementName) + { + return $schemaElementName; + } + + /** + * Maximum length of any given database identifier, like tables or column names. + * + * @return integer + */ + public function getMaxIdentifierLength() + { + return 63; + } + + /** + * Returns the insert SQL for an empty insert statement. + * + * @param string $tableName + * @param string $identifierColumnName + * + * @return string + */ + public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName) + { + return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (null)'; + } + + /** + * Generates a Truncate Table SQL statement for a given table. + * + * Cascade is not supported on many platforms but would optionally cascade the truncate by + * following the foreign keys. + * + * @param string $tableName + * @param boolean $cascade + * + * @return string + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + $tableIdentifier = new Identifier($tableName); + + return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this); + } + + /** + * This is for test reasons, many vendors have special requirements for dummy statements. + * + * @return string + */ + public function getDummySelectSQL() + { + return 'SELECT 1'; + } + + /** + * Returns the SQL to create a new savepoint. + * + * @param string $savepoint + * + * @return string + */ + public function createSavePoint($savepoint) + { + return 'SAVEPOINT ' . $savepoint; + } + + /** + * Returns the SQL to release a savepoint. + * + * @param string $savepoint + * + * @return string + */ + public function releaseSavePoint($savepoint) + { + return 'RELEASE SAVEPOINT ' . $savepoint; + } + + /** + * Returns the SQL to rollback a savepoint. + * + * @param string $savepoint + * + * @return string + */ + public function rollbackSavePoint($savepoint) + { + return 'ROLLBACK TO SAVEPOINT ' . $savepoint; + } + + /** + * Returns the keyword list instance of this platform. + * + * @return \Doctrine\DBAL\Platforms\Keywords\KeywordList + * + * @throws \Doctrine\DBAL\DBALException If no keyword list is specified. + */ + final public function getReservedKeywordsList() + { + // Check for an existing instantiation of the keywords class. + if ($this->_keywords) { + return $this->_keywords; + } + + $class = $this->getReservedKeywordsClass(); + $keywords = new $class; + if ( ! $keywords instanceof \Doctrine\DBAL\Platforms\Keywords\KeywordList) { + throw DBALException::notSupported(__METHOD__); + } + + // Store the instance so it doesn't need to be generated on every request. + $this->_keywords = $keywords; + + return $keywords; + } + + /** + * Returns the class name of the reserved keywords list. + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + protected function getReservedKeywordsClass() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Quotes a literal string. + * This method is NOT meant to fix SQL injections! + * It is only meant to escape this platform's string literal + * quote character inside the given literal string. + * + * @param string $str The literal string to be quoted. + * + * @return string The quoted literal string. + */ + public function quoteStringLiteral($str) + { + $c = $this->getStringLiteralQuoteCharacter(); + + return $c . str_replace($c, $c . $c, $str) . $c; + } + + /** + * Gets the character used for string literal quoting. + * + * @return string + */ + public function getStringLiteralQuoteCharacter() + { + return "'"; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DB2Platform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DB2Platform.php new file mode 100644 index 00000000..da0d77ce --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DB2Platform.php @@ -0,0 +1,882 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ColumnDiff; +use Doctrine\DBAL\Schema\Identifier; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\Types\Type; + +class DB2Platform extends AbstractPlatform +{ + /** + * {@inheritdoc} + */ + public function getBinaryMaxLength() + { + return 32704; + } + + /** + * {@inheritdoc} + */ + public function getBinaryDefaultLength() + { + return 1; + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $field) + { + // todo blob(n) with $field['length']; + return 'BLOB(1M)'; + } + + /** + * {@inheritDoc} + */ + public function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'smallint' => 'smallint', + 'bigint' => 'bigint', + 'integer' => 'integer', + 'time' => 'time', + 'date' => 'date', + 'varchar' => 'string', + 'character' => 'string', + 'varbinary' => 'binary', + 'binary' => 'binary', + 'clob' => 'text', + 'blob' => 'blob', + 'decimal' => 'decimal', + 'double' => 'float', + 'real' => 'float', + 'timestamp' => 'datetime', + ); + } + + /** + * {@inheritdoc} + */ + public function isCommentedDoctrineType(Type $doctrineType) + { + if ($doctrineType->getName() === Type::BOOLEAN) { + // We require a commented boolean type in order to distinguish between boolean and smallint + // as both (have to) map to the same native type. + return true; + } + + return parent::isCommentedDoctrineType($doctrineType); + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') + : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); + } + + /** + * {@inheritdoc} + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? 'BINARY(' . ($length ?: 255) . ')' : 'VARBINARY(' . ($length ?: 255) . ')'; + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + // todo clob(n) with $field['length']; + return 'CLOB(1M)'; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'db2'; + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $columnDef) + { + return 'SMALLINT'; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $columnDef) + { + return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef); + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $columnDef) + { + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef); + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $columnDef) + { + return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef); + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + $autoinc = ''; + if ( ! empty($columnDef['autoincrement'])) { + $autoinc = ' GENERATED BY DEFAULT AS IDENTITY'; + } + + return $autoinc; + } + + /** + * {@inheritdoc} + */ + public function getBitAndComparisonExpression($value1, $value2) + { + return 'BITAND(' . $value1 . ', ' . $value2 . ')'; + } + + /** + * {@inheritdoc} + */ + public function getBitOrComparisonExpression($value1, $value2) + { + return 'BITOR(' . $value1 . ', ' . $value2 . ')'; + } + + /** + * {@inheritdoc} + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + switch ($unit) { + case self::DATE_INTERVAL_UNIT_WEEK: + $interval *= 7; + $unit = self::DATE_INTERVAL_UNIT_DAY; + break; + + case self::DATE_INTERVAL_UNIT_QUARTER: + $interval *= 3; + $unit = self::DATE_INTERVAL_UNIT_MONTH; + break; + } + + return $date . ' ' . $operator . ' ' . $interval . ' ' . $unit; + } + + /** + * {@inheritdoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return 'DAYS(' . $date1 . ') - DAYS(' . $date2 . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) { + return "TIMESTAMP(0) WITH DEFAULT"; + } + + return 'TIMESTAMP(0)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIME'; + } + + /** + * {@inheritdoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + $tableIdentifier = new Identifier($tableName); + + return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this) . ' IMMEDIATE'; + } + + /** + * This code fragment is originally from the Zend_Db_Adapter_Db2 class, but has been edited. + * + * @license New BSD License + * + * @param string $table + * @param string $database + * + * @return string + */ + public function getListTableColumnsSQL($table, $database = null) + { + $table = $this->quoteStringLiteral($table); + + // We do the funky subquery and join syscat.columns.default this crazy way because + // as of db2 v10, the column is CLOB(64k) and the distinct operator won't allow a CLOB, + // it wants shorter stuff like a varchar. + return " + SELECT + cols.default, + subq.* + FROM ( + SELECT DISTINCT + c.tabschema, + c.tabname, + c.colname, + c.colno, + c.typename, + c.nulls, + c.length, + c.scale, + c.identity, + tc.type AS tabconsttype, + c.remarks AS comment, + k.colseq, + CASE + WHEN c.generated = 'D' THEN 1 + ELSE 0 + END AS autoincrement + FROM syscat.columns c + LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc + ON (k.tabschema = tc.tabschema + AND k.tabname = tc.tabname + AND tc.type = 'P')) + ON (c.tabschema = k.tabschema + AND c.tabname = k.tabname + AND c.colname = k.colname) + WHERE UPPER(c.tabname) = UPPER(" . $table . ") + ORDER BY c.colno + ) subq + JOIN syscat.columns cols + ON subq.tabschema = cols.tabschema + AND subq.tabname = cols.tabname + AND subq.colno = cols.colno + ORDER BY subq.colno + "; + } + + /** + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return "SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = 'T'"; + } + + /** + * {@inheritDoc} + */ + public function getListViewsSQL($database) + { + return "SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS"; + } + + /** + * {@inheritDoc} + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + $table = $this->quoteStringLiteral($table); + + return "SELECT idx.INDNAME AS key_name, + idxcol.COLNAME AS column_name, + CASE + WHEN idx.UNIQUERULE = 'P' THEN 1 + ELSE 0 + END AS primary, + CASE + WHEN idx.UNIQUERULE = 'D' THEN 1 + ELSE 0 + END AS non_unique + FROM SYSCAT.INDEXES AS idx + JOIN SYSCAT.INDEXCOLUSE AS idxcol + ON idx.INDSCHEMA = idxcol.INDSCHEMA AND idx.INDNAME = idxcol.INDNAME + WHERE idx.TABNAME = UPPER(" . $table . ") + ORDER BY idxcol.COLSEQ ASC"; + } + + /** + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table) + { + $table = $this->quoteStringLiteral($table); + + return "SELECT fkcol.COLNAME AS local_column, + fk.REFTABNAME AS foreign_table, + pkcol.COLNAME AS foreign_column, + fk.CONSTNAME AS index_name, + CASE + WHEN fk.UPDATERULE = 'R' THEN 'RESTRICT' + ELSE NULL + END AS on_update, + CASE + WHEN fk.DELETERULE = 'C' THEN 'CASCADE' + WHEN fk.DELETERULE = 'N' THEN 'SET NULL' + WHEN fk.DELETERULE = 'R' THEN 'RESTRICT' + ELSE NULL + END AS on_delete + FROM SYSCAT.REFERENCES AS fk + JOIN SYSCAT.KEYCOLUSE AS fkcol + ON fk.CONSTNAME = fkcol.CONSTNAME + AND fk.TABSCHEMA = fkcol.TABSCHEMA + AND fk.TABNAME = fkcol.TABNAME + JOIN SYSCAT.KEYCOLUSE AS pkcol + ON fk.REFKEYNAME = pkcol.CONSTNAME + AND fk.REFTABSCHEMA = pkcol.TABSCHEMA + AND fk.REFTABNAME = pkcol.TABNAME + WHERE fk.TABNAME = UPPER(" . $table . ") + ORDER BY fkcol.COLSEQ ASC"; + } + + /** + * {@inheritDoc} + */ + public function getCreateViewSQL($name, $sql) + { + return "CREATE VIEW ".$name." AS ".$sql; + } + + /** + * {@inheritDoc} + */ + public function getDropViewSQL($name) + { + return "DROP VIEW ".$name; + } + + /** + * {@inheritDoc} + */ + public function getCreateDatabaseSQL($database) + { + return "CREATE DATABASE ".$database; + } + + /** + * {@inheritDoc} + */ + public function getDropDatabaseSQL($database) + { + return "DROP DATABASE " . $database; + } + + /** + * {@inheritDoc} + */ + public function supportsCreateDropDatabase() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function supportsReleaseSavepoints() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function supportsCommentOnStatement() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getCurrentDateSQL() + { + return 'CURRENT DATE'; + } + + /** + * {@inheritDoc} + */ + public function getCurrentTimeSQL() + { + return 'CURRENT TIME'; + } + + /** + * {@inheritDoc} + */ + public function getCurrentTimestampSQL() + { + return "CURRENT TIMESTAMP"; + } + + /** + * {@inheritDoc} + */ + public function getIndexDeclarationSQL($name, Index $index) + { + // Index declaration in statements like CREATE TABLE is not supported. + throw DBALException::notSupported(__METHOD__); + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) + { + $indexes = array(); + if (isset($options['indexes'])) { + $indexes = $options['indexes']; + } + $options['indexes'] = array(); + + $sqls = parent::_getCreateTableSQL($tableName, $columns, $options); + + foreach ($indexes as $definition) { + $sqls[] = $this->getCreateIndexSQL($definition, $tableName); + } + return $sqls; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = array(); + $columnSql = array(); + $commentsSQL = array(); + + $queryParts = array(); + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $columnDef = $column->toArray(); + $queryPart = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef); + + // Adding non-nullable columns to a table requires a default value to be specified. + if ( ! empty($columnDef['notnull']) && + ! isset($columnDef['default']) && + empty($columnDef['autoincrement']) + ) { + $queryPart .= ' WITH DEFAULT'; + } + + $queryParts[] = $queryPart; + + $comment = $this->getColumnComment($column); + + if (null !== $comment && '' !== $comment) { + $commentsSQL[] = $this->getCommentOnColumnSQL( + $diff->getName($this)->getQuotedName($this), + $column->getQuotedName($this), + $comment + ); + } + } + + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this); + } + + foreach ($diff->changedColumns as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + if ($columnDiff->hasChanged('comment')) { + $commentsSQL[] = $this->getCommentOnColumnSQL( + $diff->getName($this)->getQuotedName($this), + $columnDiff->column->getQuotedName($this), + $this->getColumnComment($columnDiff->column) + ); + + if (count($columnDiff->changedProperties) === 1) { + continue; + } + } + + $this->gatherAlterColumnSQL($diff->fromTable, $columnDiff, $sql, $queryParts); + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $oldColumnName = new Identifier($oldColumnName); + + $queryParts[] = 'RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . + ' TO ' . $column->getQuotedName($this); + } + + $tableSql = array(); + + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + if (count($queryParts) > 0) { + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . implode(" ", $queryParts); + } + + // Some table alteration operations require a table reorganization. + if ( ! empty($diff->removedColumns) || ! empty($diff->changedColumns)) { + $sql[] = "CALL SYSPROC.ADMIN_CMD ('REORG TABLE " . $diff->getName($this)->getQuotedName($this) . "')"; + } + + $sql = array_merge($sql, $commentsSQL); + + if ($diff->newName !== false) { + $sql[] = 'RENAME TABLE ' . $diff->getName($this)->getQuotedName($this) . ' TO ' . $diff->getNewName()->getQuotedName($this); + } + + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff) + ); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * Gathers the table alteration SQL for a given column diff. + * + * @param Table $table The table to gather the SQL for. + * @param ColumnDiff $columnDiff The column diff to evaluate. + * @param array $sql The sequence of table alteration statements to fill. + * @param array $queryParts The sequence of column alteration clauses to fill. + */ + private function gatherAlterColumnSQL(Table $table, ColumnDiff $columnDiff, array &$sql, array &$queryParts) + { + $alterColumnClauses = $this->getAlterColumnClausesSQL($columnDiff); + + if (empty($alterColumnClauses)) { + return; + } + + // If we have a single column alteration, we can append the clause to the main query. + if (count($alterColumnClauses) === 1) { + $queryParts[] = current($alterColumnClauses); + + return; + } + + // We have multiple alterations for the same column, + // so we need to trigger a complete ALTER TABLE statement + // for each ALTER COLUMN clause. + foreach ($alterColumnClauses as $alterColumnClause) { + $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ' . $alterColumnClause; + } + } + + /** + * Returns the ALTER COLUMN SQL clauses for altering a column described by the given column diff. + * + * @param ColumnDiff $columnDiff The column diff to evaluate. + * + * @return array + */ + private function getAlterColumnClausesSQL(ColumnDiff $columnDiff) + { + $column = $columnDiff->column->toArray(); + + $alterClause = 'ALTER COLUMN ' . $columnDiff->column->getQuotedName($this); + + if ($column['columnDefinition']) { + return array($alterClause . ' ' . $column['columnDefinition']); + } + + $clauses = array(); + + if ($columnDiff->hasChanged('type') || + $columnDiff->hasChanged('length') || + $columnDiff->hasChanged('precision') || + $columnDiff->hasChanged('scale') || + $columnDiff->hasChanged('fixed') + ) { + $clauses[] = $alterClause . ' SET DATA TYPE ' . $column['type']->getSQLDeclaration($column, $this); + } + + if ($columnDiff->hasChanged('notnull')) { + $clauses[] = $column['notnull'] ? $alterClause . ' SET NOT NULL' : $alterClause . ' DROP NOT NULL'; + } + + if ($columnDiff->hasChanged('default')) { + if (isset($column['default'])) { + $defaultClause = $this->getDefaultValueDeclarationSQL($column); + + if ($defaultClause) { + $clauses[] = $alterClause . ' SET' . $defaultClause; + } + } else { + $clauses[] = $alterClause . ' DROP DEFAULT'; + } + } + + return $clauses; + } + + /** + * {@inheritDoc} + */ + protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) + { + $sql = array(); + $table = $diff->getName($this)->getQuotedName($this); + + foreach ($diff->removedIndexes as $remKey => $remIndex) { + foreach ($diff->addedIndexes as $addKey => $addIndex) { + if ($remIndex->getColumns() == $addIndex->getColumns()) { + if ($remIndex->isPrimary()) { + $sql[] = 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY'; + } elseif ($remIndex->isUnique()) { + $sql[] = 'ALTER TABLE ' . $table . ' DROP UNIQUE ' . $remIndex->getQuotedName($this); + } else { + $sql[] = $this->getDropIndexSQL($remIndex, $table); + } + + $sql[] = $this->getCreateIndexSQL($addIndex, $table); + + unset($diff->removedIndexes[$remKey]); + unset($diff->addedIndexes[$addKey]); + + break; + } + } + } + + $sql = array_merge($sql, parent::getPreAlterTableIndexForeignKeySQL($diff)); + + return $sql; + } + + /** + * {@inheritdoc} + */ + protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) + { + if (strpos($tableName, '.') !== false) { + list($schema) = explode('.', $tableName); + $oldIndexName = $schema . '.' . $oldIndexName; + } + + return array('RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)); + } + + /** + * {@inheritDoc} + */ + public function getDefaultValueDeclarationSQL($field) + { + if ( ! empty($field['autoincrement'])) { + return ''; + } + + if (isset($field['version']) && $field['version']) { + if ((string) $field['type'] != "DateTime") { + $field['default'] = "1"; + } + } + + return parent::getDefaultValueDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName) + { + return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (DEFAULT)'; + } + + /** + * {@inheritDoc} + */ + public function getCreateTemporaryTableSnippetSQL() + { + return "DECLARE GLOBAL TEMPORARY TABLE"; + } + + /** + * {@inheritDoc} + */ + public function getTemporaryTableName($tableName) + { + return "SESSION." . $tableName; + } + + /** + * {@inheritDoc} + */ + protected function doModifyLimitQuery($query, $limit, $offset = null) + { + $where = array(); + + if ($offset > 0) { + $where[] = sprintf('db22.DC_ROWNUM >= %d', $offset + 1); + } + + if ($limit !== null) { + $where[] = sprintf('db22.DC_ROWNUM <= %d', $offset + $limit); + } + + if (empty($where)) { + return $query; + } + + // Todo OVER() needs ORDER BY data! + return sprintf( + 'SELECT db22.* FROM (SELECT db21.*, ROW_NUMBER() OVER() AS DC_ROWNUM FROM (%s) db21) db22 WHERE %s', + $query, + implode(' AND ', $where) + ); + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'LOCATE(' . $substr . ', ' . $str . ')'; + } + + return 'LOCATE(' . $substr . ', ' . $str . ', '.$startPos.')'; + } + + /** + * {@inheritDoc} + */ + public function getSubstringExpression($value, $from, $length = null) + { + if ($length === null) { + return 'SUBSTR(' . $value . ', ' . $from . ')'; + } + + return 'SUBSTR(' . $value . ', ' . $from . ', ' . $length . ')'; + } + + /** + * {@inheritDoc} + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function prefersIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + * + * DB2 returns all column names in SQL result sets in uppercase. + */ + public function getSQLResultCasing($column) + { + return strtoupper($column); + } + + /** + * {@inheritDoc} + */ + public function getForUpdateSQL() + { + return ' WITH RR USE AND KEEP UPDATE LOCKS'; + } + + /** + * {@inheritDoc} + */ + public function getDummySelectSQL() + { + return 'SELECT 1 FROM sysibm.sysdummy1'; + } + + /** + * {@inheritDoc} + * + * DB2 supports savepoints, but they work semantically different than on other vendor platforms. + * + * TODO: We have to investigate how to get DB2 up and running with savepoints. + */ + public function supportsSavepoints() + { + return false; + } + + /** + * {@inheritDoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\DB2Keywords'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DrizzlePlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DrizzlePlatform.php new file mode 100644 index 00000000..08f053b0 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DrizzlePlatform.php @@ -0,0 +1,624 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\Schema\Identifier; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Types\BinaryType; + +/** + * Drizzle platform + * + * @author Kim Hemsø Rasmussen + */ +class DrizzlePlatform extends AbstractPlatform +{ + /** + * {@inheritDoc} + */ + public function getName() + { + return 'drizzle'; + } + + /** + * {@inheritDoc} + */ + public function getIdentifierQuoteCharacter() + { + return '`'; + } + + /** + * {@inheritDoc} + */ + public function getConcatExpression() + { + $args = func_get_args(); + + return 'CONCAT(' . join(', ', (array) $args) . ')'; + } + + /** + * {@inheritdoc} + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + $function = '+' === $operator ? 'DATE_ADD' : 'DATE_SUB'; + + return $function . '(' . $date . ', INTERVAL ' . $interval . ' ' . $unit . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return 'DATEDIFF(' . $date1 . ', ' . $date2 . ')'; + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $field) + { + return 'BOOLEAN'; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $field) + { + return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + $autoinc = ''; + if ( ! empty($columnDef['autoincrement'])) { + $autoinc = ' AUTO_INCREMENT'; + } + + return $autoinc; + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $field) + { + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $field) + { + return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'; + } + + /** + * {@inheritdoc} + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) + { + return 'VARBINARY(' . ($length ?: 255) . ')'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'boolean' => 'boolean', + 'varchar' => 'string', + 'varbinary' => 'binary', + 'integer' => 'integer', + 'blob' => 'blob', + 'decimal' => 'decimal', + 'datetime' => 'datetime', + 'date' => 'date', + 'time' => 'time', + 'text' => 'text', + 'timestamp' => 'datetime', + 'double' => 'float', + 'bigint' => 'bigint', + ); + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + return 'TEXT'; + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $field) + { + return 'BLOB'; + } + + /** + * {@inheritDoc} + */ + public function getCreateDatabaseSQL($name) + { + return 'CREATE DATABASE ' . $name; + } + + /** + * {@inheritDoc} + */ + public function getDropDatabaseSQL($name) + { + return 'DROP DATABASE ' . $name; + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) + { + $queryFields = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + foreach ($options['uniqueConstraints'] as $index => $definition) { + $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition); + } + } + + // add all indexes + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach ($options['indexes'] as $index => $definition) { + $queryFields .= ', ' . $this->getIndexDeclarationSQL($index, $definition); + } + } + + // attach all primary keys + if (isset($options['primary']) && ! empty($options['primary'])) { + $keyColumns = array_unique(array_values($options['primary'])); + $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; + } + + $query = 'CREATE '; + + if (!empty($options['temporary'])) { + $query .= 'TEMPORARY '; + } + + $query .= 'TABLE ' . $tableName . ' (' . $queryFields . ') '; + $query .= $this->buildTableOptions($options); + $query .= $this->buildPartitionOptions($options); + + $sql[] = $query; + + if (isset($options['foreignKeys'])) { + foreach ((array) $options['foreignKeys'] as $definition) { + $sql[] = $this->getCreateForeignKeySQL($definition, $tableName); + } + } + + return $sql; + } + + /** + * Build SQL for table options + * + * @param array $options + * + * @return string + */ + private function buildTableOptions(array $options) + { + if (isset($options['table_options'])) { + return $options['table_options']; + } + + $tableOptions = array(); + + // Collate + if ( ! isset($options['collate'])) { + $options['collate'] = 'utf8_unicode_ci'; + } + + $tableOptions[] = sprintf('COLLATE %s', $options['collate']); + + // Engine + if ( ! isset($options['engine'])) { + $options['engine'] = 'InnoDB'; + } + + $tableOptions[] = sprintf('ENGINE = %s', $options['engine']); + + // Auto increment + if (isset($options['auto_increment'])) { + $tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']); + } + + // Comment + if (isset($options['comment'])) { + $comment = trim($options['comment'], " '"); + + $tableOptions[] = sprintf("COMMENT = %s ", $this->quoteStringLiteral($comment)); + } + + // Row format + if (isset($options['row_format'])) { + $tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']); + } + + return implode(' ', $tableOptions); + } + + /** + * Build SQL for partition options. + * + * @param array $options + * + * @return string + */ + private function buildPartitionOptions(array $options) + { + return (isset($options['partition_options'])) + ? ' ' . $options['partition_options'] + : ''; + } + + /** + * {@inheritDoc} + */ + public function getListDatabasesSQL() + { + return "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE CATALOG_NAME='LOCAL'"; + } + + /** + * {@inheritDoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\DrizzleKeywords'; + } + + /** + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE' AND TABLE_SCHEMA=DATABASE()"; + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + if ($database) { + $database = "'" . $database . "'"; + } else { + $database = 'DATABASE()'; + } + + return "SELECT COLUMN_NAME, DATA_TYPE, COLUMN_COMMENT, IS_NULLABLE, IS_AUTO_INCREMENT, CHARACTER_MAXIMUM_LENGTH, COLUMN_DEFAULT," . + " NUMERIC_PRECISION, NUMERIC_SCALE, COLLATION_NAME" . + " FROM DATA_DICTIONARY.COLUMNS" . + " WHERE TABLE_SCHEMA=" . $database . " AND TABLE_NAME = '" . $table . "'"; + } + + /** + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table, $database = null) + { + if ($database) { + $database = "'" . $database . "'"; + } else { + $database = 'DATABASE()'; + } + + return "SELECT CONSTRAINT_NAME, CONSTRAINT_COLUMNS, REFERENCED_TABLE_NAME, REFERENCED_TABLE_COLUMNS, UPDATE_RULE, DELETE_RULE" . + " FROM DATA_DICTIONARY.FOREIGN_KEYS" . + " WHERE CONSTRAINT_SCHEMA=" . $database . " AND CONSTRAINT_TABLE='" . $table . "'"; + } + + /** + * {@inheritDoc} + */ + public function getListTableIndexesSQL($table, $database = null) + { + if ($database) { + $database = "'" . $database . "'"; + } else { + $database = 'DATABASE()'; + } + + return "SELECT INDEX_NAME AS 'key_name', COLUMN_NAME AS 'column_name', IS_USED_IN_PRIMARY AS 'primary', IS_UNIQUE=0 AS 'non_unique'" . + " FROM DATA_DICTIONARY.INDEX_PARTS" . + " WHERE TABLE_SCHEMA=" . $database . " AND TABLE_NAME='" . $table . "'"; + } + + /** + * {@inheritDoc} + */ + public function prefersIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsInlineColumnComments() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsViews() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function supportsColumnCollation() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getDropIndexSQL($index, $table=null) + { + if ($index instanceof Index) { + $indexName = $index->getQuotedName($this); + } elseif (is_string($index)) { + $indexName = $index; + } else { + throw new \InvalidArgumentException('DrizzlePlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.'); + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } elseif (!is_string($table)) { + throw new \InvalidArgumentException('DrizzlePlatform::getDropIndexSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); + } + + if ($index instanceof Index && $index->isPrimary()) { + // drizzle primary keys are always named "PRIMARY", + // so we cannot use them in statements because of them being keyword. + return $this->getDropPrimaryKeySQL($table); + } + + return 'DROP INDEX ' . $indexName . ' ON ' . $table; + } + + /** + * {@inheritDoc} + */ + protected function getDropPrimaryKeySQL($table) + { + return 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) { + return 'TIMESTAMP'; + } + + return 'DATETIME'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIME'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $columnSql = array(); + $queryParts = array(); + + if ($diff->newName !== false) { + $queryParts[] = 'RENAME TO ' . $diff->getNewName()->getQuotedName($this); + } + + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $columnArray = $column->toArray(); + $columnArray['comment'] = $this->getColumnComment($column); + $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); + } + + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $queryParts[] = 'DROP ' . $column->getQuotedName($this); + } + + foreach ($diff->changedColumns as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + /* @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */ + $column = $columnDiff->column; + $columnArray = $column->toArray(); + + // Do not generate column alteration clause if type is binary and only fixed property has changed. + // Drizzle only supports binary type columns with variable length. + // Avoids unnecessary table alteration statements. + if ($columnArray['type'] instanceof BinaryType && + $columnDiff->hasChanged('fixed') && + count($columnDiff->changedProperties) === 1 + ) { + continue; + } + + $columnArray['comment'] = $this->getColumnComment($column); + $queryParts[] = 'CHANGE ' . ($columnDiff->getOldColumnName()->getQuotedName($this)) . ' ' + . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $oldColumnName = new Identifier($oldColumnName); + + $columnArray = $column->toArray(); + $columnArray['comment'] = $this->getColumnComment($column); + $queryParts[] = 'CHANGE ' . $oldColumnName->getQuotedName($this) . ' ' + . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); + } + + $sql = array(); + $tableSql = array(); + + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + if (count($queryParts) > 0) { + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . implode(", ", $queryParts); + } + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff) + ); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * {@inheritDoc} + */ + public function getDropTemporaryTableSQL($table) + { + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } elseif (!is_string($table)) { + throw new \InvalidArgumentException('getDropTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); + } + + return 'DROP TEMPORARY TABLE ' . $table; + } + + /** + * {@inheritDoc} + */ + public function convertBooleans($item) + { + if (is_array($item)) { + foreach ($item as $key => $value) { + if (is_bool($value) || is_numeric($item)) { + $item[$key] = ($value) ? 'true' : 'false'; + } + } + } elseif (is_bool($item) || is_numeric($item)) { + $item = ($item) ? 'true' : 'false'; + } + + return $item; + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'LOCATE(' . $substr . ', ' . $str . ')'; + } + + return 'LOCATE(' . $substr . ', ' . $str . ', '.$startPos.')'; + } + + /** + * {@inheritDoc} + */ + public function getGuidExpression() + { + return 'UUID()'; + } + + /** + * {@inheritDoc} + */ + public function getRegexpExpression() + { + return 'RLIKE'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DB2Keywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DB2Keywords.php new file mode 100644 index 00000000..ff8aac81 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DB2Keywords.php @@ -0,0 +1,441 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * DB2 Keywords. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class DB2Keywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'DB2'; + } + + /** + * {@inheritdoc} + */ + protected function getKeywords() + { + return array( + 'ACTIVATE', + 'ADD', + 'AFTER', + 'ALIAS', + 'ALL', + 'ALLOCATE', + 'DOCUMENT', + 'DOUBLE', + 'DROP', + 'DSSIZE', + 'DYNAMIC', + 'EACH', + 'LOCK', + 'LOCKMAX', + 'LOCKSIZE', + 'LONG', + 'LOOP', + 'MAINTAINED', + 'ROUND_CEILING', + 'ROUND_DOWN', + 'ROUND_FLOOR', + 'ROUND_HALF_DOWN', + 'ROUND_HALF_EVEN', + 'ROUND_HALF_UP', + 'ALLOW', + 'ALTER', + 'AND', + 'ANY', + 'AS', + 'ASENSITIVE', + 'ASSOCIATE', + 'ASUTIME', + 'AT', + 'ATTRIBUTES', + 'AUDIT', + 'AUTHORIZATION', + 'AUX', + 'AUXILIARY', + 'BEFORE', + 'BEGIN', + 'BETWEEN', + 'BINARY', + 'BUFFERPOOL', + 'BY', + 'CACHE', + 'CALL', + 'CALLED', + 'CAPTURE', + 'CARDINALITY', + 'CASCADED', + 'CASE', + 'CAST', + 'CCSID', + 'CHAR', + 'CHARACTER', + 'CHECK', + 'CLONE', + 'CLOSE', + 'CLUSTER', + 'COLLECTION', + 'COLLID', + 'COLUMN', + 'COMMENT', + 'COMMIT', + 'CONCAT', + 'CONDITION', + 'CONNECT', + 'CONNECTION', + 'CONSTRAINT', + 'CONTAINS', + 'CONTINUE', + 'COUNT', + 'COUNT_BIG', + 'CREATE', + 'CROSS', + 'CURRENT', + 'CURRENT_DATE', + 'CURRENT_LC_CTYPE', + 'CURRENT_PATH', + 'CURRENT_SCHEMA', + 'CURRENT_SERVER', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_TIMEZONE', + 'CURRENT_USER', + 'CURSOR', + 'CYCLE', + 'DATA', + 'DATABASE', + 'DATAPARTITIONNAME', + 'DATAPARTITIONNUM', + 'EDITPROC', + 'ELSE', + 'ELSEIF', + 'ENABLE', + 'ENCODING', + 'ENCRYPTION', + 'END', + 'END-EXEC', + 'ENDING', + 'ERASE', + 'ESCAPE', + 'EVERY', + 'EXCEPT', + 'EXCEPTION', + 'EXCLUDING', + 'EXCLUSIVE', + 'EXECUTE', + 'EXISTS', + 'EXIT', + 'EXPLAIN', + 'EXTERNAL', + 'EXTRACT', + 'FENCED', + 'FETCH', + 'FIELDPROC', + 'FILE', + 'FINAL', + 'FOR', + 'FOREIGN', + 'FREE', + 'FROM', + 'FULL', + 'FUNCTION', + 'GENERAL', + 'GENERATED', + 'GET', + 'GLOBAL', + 'GO', + 'GOTO', + 'GRANT', + 'GRAPHIC', + 'GROUP', + 'HANDLER', + 'HASH', + 'HASHED_VALUE', + 'HAVING', + 'HINT', + 'HOLD', + 'HOUR', + 'HOURS', + 'IDENTITY', + 'IF', + 'IMMEDIATE', + 'IN', + 'INCLUDING', + 'INCLUSIVE', + 'INCREMENT', + 'INDEX', + 'INDICATOR', + 'INF', + 'INFINITY', + 'INHERIT', + 'INNER', + 'INOUT', + 'INSENSITIVE', + 'INSERT', + 'INTEGRITY', + 'MATERIALIZED', + 'MAXVALUE', + 'MICROSECOND', + 'MICROSECONDS', + 'MINUTE', + 'MINUTES', + 'MINVALUE', + 'MODE', + 'MODIFIES', + 'MONTH', + 'MONTHS', + 'NAN', + 'NEW', + 'NEW_TABLE', + 'NEXTVAL', + 'NO', + 'NOCACHE', + 'NOCYCLE', + 'NODENAME', + 'NODENUMBER', + 'NOMAXVALUE', + 'NOMINVALUE', + 'NONE', + 'NOORDER', + 'NORMALIZED', + 'NOT', + 'NULL', + 'NULLS', + 'NUMPARTS', + 'OBID', + 'OF', + 'OLD', + 'OLD_TABLE', + 'ON', + 'OPEN', + 'OPTIMIZATION', + 'OPTIMIZE', + 'OPTION', + 'OR', + 'ORDER', + 'OUT', + 'OUTER', + 'OVER', + 'OVERRIDING', + 'PACKAGE', + 'PADDED', + 'PAGESIZE', + 'PARAMETER', + 'PART', + 'PARTITION', + 'PARTITIONED', + 'PARTITIONING', + 'PARTITIONS', + 'PASSWORD', + 'PATH', + 'PIECESIZE', + 'PLAN', + 'POSITION', + 'PRECISION', + 'PREPARE', + 'PREVVAL', + 'PRIMARY', + 'PRIQTY', + 'PRIVILEGES', + 'PROCEDURE', + 'PROGRAM', + 'PSID', + 'ROUND_UP', + 'ROUTINE', + 'ROW', + 'ROW_NUMBER', + 'ROWNUMBER', + 'ROWS', + 'ROWSET', + 'RRN', + 'RUN', + 'SAVEPOINT', + 'SCHEMA', + 'SCRATCHPAD', + 'SCROLL', + 'SEARCH', + 'SECOND', + 'SECONDS', + 'SECQTY', + 'SECURITY', + 'SELECT', + 'SENSITIVE', + 'SEQUENCE', + 'SESSION', + 'SESSION_USER', + 'SET', + 'SIGNAL', + 'SIMPLE', + 'SNAN', + 'SOME', + 'SOURCE', + 'SPECIFIC', + 'SQL', + 'SQLID', + 'STACKED', + 'STANDARD', + 'START', + 'STARTING', + 'STATEMENT', + 'STATIC', + 'STATMENT', + 'STAY', + 'STOGROUP', + 'STORES', + 'STYLE', + 'SUBSTRING', + 'SUMMARY', + 'SYNONYM', + 'SYSFUN', + 'SYSIBM', + 'SYSPROC', + 'SYSTEM', + 'SYSTEM_USER', + 'TABLE', + 'TABLESPACE', + 'THEN', + 'TIME', + 'TIMESTAMP', + 'TO', + 'TRANSACTION', + 'TRIGGER', + 'TRIM', + 'TRUNCATE', + 'TYPE', + 'UNDO', + 'UNION', + 'UNIQUE', + 'UNTIL', + 'UPDATE', + 'DATE', + 'DAY', + 'DAYS', + 'DB2GENERAL', + 'DB2GENRL', + 'DB2SQL', + 'DBINFO', + 'DBPARTITIONNAME', + 'DBPARTITIONNUM', + 'DEALLOCATE', + 'DECLARE', + 'DEFAULT', + 'DEFAULTS', + 'DEFINITION', + 'DELETE', + 'DENSE_RANK', + 'DENSERANK', + 'DESCRIBE', + 'DESCRIPTOR', + 'DETERMINISTIC', + 'DIAGNOSTICS', + 'DISABLE', + 'DISALLOW', + 'DISCONNECT', + 'DISTINCT', + 'DO', + 'INTERSECT', + 'PUBLIC', + 'USAGE', + 'INTO', + 'QUERY', + 'USER', + 'IS', + 'QUERYNO', + 'USING', + 'ISOBID', + 'RANGE', + 'VALIDPROC', + 'ISOLATION', + 'RANK', + 'VALUE', + 'ITERATE', + 'READ', + 'VALUES', + 'JAR', + 'READS', + 'VARIABLE', + 'JAVA', + 'RECOVERY', + 'VARIANT', + 'JOIN', + 'REFERENCES', + 'VCAT', + 'KEEP', + 'REFERENCING', + 'VERSION', + 'KEY', + 'REFRESH', + 'VIEW', + 'LABEL', + 'RELEASE', + 'VOLATILE', + 'LANGUAGE', + 'RENAME', + 'VOLUMES', + 'LATERAL', + 'REPEAT', + 'WHEN', + 'LC_CTYPE', + 'RESET', + 'WHENEVER', + 'LEAVE', + 'RESIGNAL', + 'WHERE', + 'LEFT', + 'RESTART', + 'WHILE', + 'LIKE', + 'RESTRICT', + 'WITH', + 'LINKTYPE', + 'RESULT', + 'WITHOUT', + 'LOCAL', + 'RESULT_SET_LOCATOR WLM', + 'LOCALDATE', + 'RETURN', + 'WRITE', + 'LOCALE', + 'RETURNS', + 'XMLELEMENT', + 'LOCALTIME', + 'REVOKE', + 'XMLEXISTS', + 'LOCALTIMESTAMP RIGHT', + 'XMLNAMESPACES', + 'LOCATOR', + 'ROLE', + 'YEAR', + 'LOCATORS', + 'ROLLBACK', + 'YEARS', + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DrizzleKeywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DrizzleKeywords.php new file mode 100644 index 00000000..21d59d8c --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DrizzleKeywords.php @@ -0,0 +1,345 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * Drizzle Keywordlist. + * + * @author Kim Hemsø Rasmussen + */ +class DrizzleKeywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'drizzle'; + } + + /** + * {@inheritdoc} + */ + protected function getKeywords() + { + return array( + 'ABS', + 'ALL', + 'ALLOCATE', + 'ALTER', + 'AND', + 'ANY', + 'ARE', + 'ARRAY', + 'AS', + 'ASENSITIVE', + 'ASYMMETRIC', + 'AT', + 'ATOMIC', + 'AUTHORIZATION', + 'AVG', + 'BEGIN', + 'BETWEEN', + 'BIGINT', + 'BINARY', + 'BLOB', + 'BOOLEAN', + 'BOTH', + 'BY', + 'CALL', + 'CALLED', + 'CARDINALITY', + 'CASCADED', + 'CASE', + 'CAST', + 'CEIL', + 'CEILING', + 'CHAR', + 'CHARACTER', + 'CHARACTER_LENGTH', + 'CHAR_LENGTH', + 'CHECK', + 'CLOB', + 'CLOSE', + 'COALESCE', + 'COLLATE', + 'COLLECT', + 'COLUMN', + 'COMMIT', + 'CONDITION', + 'CONNECT', + 'CONSTRAINT', + 'CONVERT', + 'CORR', + 'CORRESPONDING', + 'COUNT', + 'COVAR_POP', + 'COVAR_SAMP', + 'CREATE', + 'CROSS', + 'CUBE', + 'CUME_DIST', + 'CURRENT', + 'CURRENT_DATE', + 'CURRENT_DEFAULT_TRANSFORM_GROUP', + 'CURRENT_PATH', + 'CURRENT_ROLE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_TRANSFORM_GROUP_FOR_TYPE', + 'CURRENT_USER', + 'CURSOR', + 'CYCLE', + 'DATE', + 'DAY', + 'DEALLOCATE', + 'DEC', + 'DECIMAL', + 'DECLARE', + 'DEFAULT', + 'DELETE', + 'DENSE_RANK', + 'DEREF', + 'DESCRIBE', + 'DETERMINISTIC', + 'DISCONNECT', + 'DISTINCT', + 'DOUBLE', + 'DROP', + 'DYNAMIC', + 'EACH', + 'ELEMENT', + 'ELSE', + 'END', + 'ESCAPE', + 'EVERY', + 'EXCEPT', + 'EXEC', + 'EXECUTE', + 'EXISTS', + 'EXP', + 'EXTERNAL', + 'EXTRACT', + 'FALSE', + 'FETCH', + 'FILTER', + 'FLOAT', + 'FLOOR', + 'FOR', + 'FOREIGN', + 'FREE', + 'FROM', + 'FULL', + 'FUNCTION', + 'FUSION', + 'GET', + 'GLOBAL', + 'GRANT', + 'GROUP', + 'GROUPING', + 'HAVING', + 'HOLD', + 'HOUR', + 'IDENTITY', + 'IN', + 'INDICATOR', + 'INNER', + 'INOUT', + 'INSENSITIVE', + 'INSERT', + 'INT', + 'INTEGER', + 'INTERSECT', + 'INTERSECTION', + 'INTERVAL', + 'INTO', + 'IS', + 'JOIN', + 'LANGUAGE', + 'LARGE', + 'LATERAL', + 'LEADING', + 'LEFT', + 'LIKE', + 'LN', + 'LOCAL', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'LOWER', + 'MATCH', + 'MAX', + 'MEMBER', + 'MERGE', + 'METHOD', + 'MIN', + 'MINUTE', + 'MOD', + 'MODIFIES', + 'MODULE', + 'MONTH', + 'MULTISET', + 'NATIONAL', + 'NATURAL', + 'NCHAR', + 'NCLOB', + 'NEW', + 'NO', + 'NONE', + 'NORMALIZE', + 'NOT', + 'NULL_SYM', + 'NULLIF', + 'NUMERIC', + 'OCTET_LENGTH', + 'OF', + 'OLD', + 'ON', + 'ONLY', + 'OPEN', + 'OR', + 'ORDER', + 'OUT', + 'OUTER', + 'OVER', + 'OVERLAPS', + 'OVERLAY', + 'PARAMETER', + 'PARTITION', + 'PERCENTILE_CONT', + 'PERCENTILE_DISC', + 'PERCENT_RANK', + 'POSITION', + 'POWER', + 'PRECISION', + 'PREPARE', + 'PRIMARY', + 'PROCEDURE', + 'RANGE', + 'RANK', + 'READS', + 'REAL', + 'RECURSIVE', + 'REF', + 'REFERENCES', + 'REFERENCING', + 'REGR_AVGX', + 'REGR_AVGY', + 'REGR_COUNT', + 'REGR_INTERCEPT', + 'REGR_R2', + 'REGR_SLOPE', + 'REGR_SXX', + 'REGR_SXY', + 'REGR_SYY', + 'RELEASE', + 'RESULT', + 'RETURN', + 'RETURNS', + 'REVOKE', + 'RIGHT', + 'ROLLBACK', + 'ROLLUP', + 'ROW', + 'ROWS', + 'ROW_NUMBER', + 'SAVEPOINT', + 'SCOPE', + 'SCROLL', + 'SEARCH', + 'SECOND', + 'SELECT', + 'SENSITIVE', + 'SESSION_USER', + 'SET', + 'SIMILAR', + 'SMALLINT', + 'SOME', + 'SPECIFIC', + 'SPECIFICTYPE', + 'SQL', + 'SQLEXCEPTION', + 'SQLSTATE', + 'SQLWARNING', + 'SQRT', + 'START', + 'STATIC', + 'STDDEV_POP', + 'STDDEV_SAMP', + 'SUBMULTISET', + 'SUBSTRING', + 'SUM', + 'SYMMETRIC', + 'SYSTEM', + 'SYSTEM_USER', + 'TABLE', + 'TABLESAMPLE', + 'THEN', + 'TIME', + 'TIMESTAMP', + 'TIMEZONE_HOUR', + 'TIMEZONE_MINUTE', + 'TO', + 'TRAILING', + 'TRANSLATE', + 'TRANSLATION', + 'TREAT', + 'TRIGGER', + 'TRIM', + 'TRUE', + 'UESCAPE', + 'UNION', + 'UNIQUE', + 'UNKNOWN', + 'UNNEST', + 'UPDATE', + 'UPPER', + 'USER', + 'USING', + 'VALUE', + 'VALUES', + 'VARCHAR', + 'VARYING', + 'VAR_POP', + 'VAR_SAMP', + 'WHEN', + 'WHENEVER', + 'WHERE', + 'WIDTH_BUCKET', + 'WINDOW', + 'WITH', + 'WITHIN', + 'WITHOUT', + 'XML', + 'XMLAGG', + 'XMLATTRIBUTES', + 'XMLBINARY', + 'XMLCOMMENT', + 'XMLCONCAT', + 'XMLELEMENT', + 'XMLFOREST', + 'XMLNAMESPACES', + 'XMLPARSE', + 'XMLPI', + 'XMLROOT', + 'XMLSERIALIZE', + 'YEAR', + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/KeywordList.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/KeywordList.php new file mode 100644 index 00000000..5b8d74d0 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/KeywordList.php @@ -0,0 +1,73 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * Abstract interface for a SQL reserved keyword dictionary. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +abstract class KeywordList +{ + /** + * @var array|null + */ + private $keywords = null; + + /** + * Checks if the given word is a keyword of this dialect/vendor platform. + * + * @param string $word + * + * @return boolean + */ + public function isKeyword($word) + { + if ($this->keywords === null) { + $this->initializeKeywords(); + } + + return isset($this->keywords[strtoupper($word)]); + } + + /** + * @return void + */ + protected function initializeKeywords() + { + $this->keywords = array_flip(array_map('strtoupper', $this->getKeywords())); + } + + /** + * Returns the list of keywords. + * + * @return array + */ + abstract protected function getKeywords(); + + /** + * Returns the name of this keyword list. + * + * @return string + */ + abstract public function getName(); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MsSQLKeywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MsSQLKeywords.php new file mode 100644 index 00000000..9c8f6149 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MsSQLKeywords.php @@ -0,0 +1,43 @@ +. + */ + + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * MsSQL Keywordlist + * + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link www.doctrine-project.com + * @since 2.0 + * @author Benjamin Eberlei + * @author David Coallier + * @author Steve Müller + * @deprecated Use SQLServerKeywords class instead. + */ +class MsSQLKeywords extends SQLServerKeywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'MsSQL'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQL57Keywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQL57Keywords.php new file mode 100644 index 00000000..7d7b2020 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQL57Keywords.php @@ -0,0 +1,281 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * MySQL 5.7 reserved keywords list. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class MySQL57Keywords extends MySQLKeywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'MySQL57'; + } + + /** + * {@inheritdoc} + * + * @link http://dev.mysql.com/doc/mysqld-version-reference/en/mysqld-version-reference-reservedwords-5-7.html + */ + protected function getKeywords() + { + return array( + 'ACCESSIBLE', + 'ADD', + 'ALL', + 'ALTER', + 'ANALYZE', + 'AND', + 'AS', + 'ASC', + 'ASENSITIVE', + 'BEFORE', + 'BETWEEN', + 'BIGINT', + 'BINARY', + 'BLOB', + 'BOTH', + 'BY', + 'CALL', + 'CASCADE', + 'CASE', + 'CHANGE', + 'CHAR', + 'CHARACTER', + 'CHECK', + 'COLLATE', + 'COLUMN', + 'CONDITION', + 'CONSTRAINT', + 'CONTINUE', + 'CONVERT', + 'CREATE', + 'CROSS', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'CURSOR', + 'DATABASE', + 'DATABASES', + 'DAY_HOUR', + 'DAY_MICROSECOND', + 'DAY_MINUTE', + 'DAY_SECOND', + 'DEC', + 'DECIMAL', + 'DECLARE', + 'DEFAULT', + 'DELAYED', + 'DELETE', + 'DESC', + 'DESCRIBE', + 'DETERMINISTIC', + 'DISTINCT', + 'DISTINCTROW', + 'DIV', + 'DOUBLE', + 'DROP', + 'DUAL', + 'EACH', + 'ELSE', + 'ELSEIF', + 'ENCLOSED', + 'ESCAPED', + 'EXISTS', + 'EXIT', + 'EXPLAIN', + 'FALSE', + 'FETCH', + 'FLOAT', + 'FLOAT4', + 'FLOAT8', + 'FOR', + 'FORCE', + 'FOREIGN', + 'FROM', + 'FULLTEXT', + 'GET', + 'GRANT', + 'GROUP', + 'HAVING', + 'HIGH_PRIORITY', + 'HOUR_MICROSECOND', + 'HOUR_MINUTE', + 'HOUR_SECOND', + 'IF', + 'IGNORE', + 'IN', + 'INDEX', + 'INFILE', + 'INNER', + 'INOUT', + 'INSENSITIVE', + 'INSERT', + 'INT', + 'INT1', + 'INT2', + 'INT3', + 'INT4', + 'INT8', + 'INTEGER', + 'INTERVAL', + 'INTO', + 'IO_AFTER_GTIDS', + 'IO_BEFORE_GTIDS', + 'IS', + 'ITERATE', + 'JOIN', + 'KEY', + 'KEYS', + 'KILL', + 'LEADING', + 'LEAVE', + 'LEFT', + 'LIKE', + 'LIMIT', + 'LINEAR', + 'LINES', + 'LOAD', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'LOCK', + 'LONG', + 'LONGBLOB', + 'LONGTEXT', + 'LOOP', + 'LOW_PRIORITY', + 'MASTER_BIND', + 'MASTER_SSL_VERIFY_SERVER_CERT', + 'MATCH', + 'MAXVALUE', + 'MEDIUMBLOB', + 'MEDIUMINT', + 'MEDIUMTEXT', + 'MIDDLEINT', + 'MINUTE_MICROSECOND', + 'MINUTE_SECOND', + 'MOD', + 'MODIFIES', + 'NATURAL', + 'NO_WRITE_TO_BINLOG', + 'NONBLOCKING', + 'NOT', + 'NULL', + 'NUMERIC', + 'ON', + 'OPTIMIZE', + 'OPTION', + 'OPTIONALLY', + 'OR', + 'ORDER', + 'OUT', + 'OUTER', + 'OUTFILE', + 'PARTITION', + 'PRECISION', + 'PRIMARY', + 'PROCEDURE', + 'PURGE', + 'RANGE', + 'READ', + 'READ_WRITE', + 'READS', + 'REAL', + 'REFERENCES', + 'REGEXP', + 'RELEASE', + 'RENAME', + 'REPEAT', + 'REPLACE', + 'REQUIRE', + 'RESIGNAL', + 'RESTRICT', + 'RETURN', + 'REVOKE', + 'RIGHT', + 'RLIKE', + 'SCHEMA', + 'SCHEMAS', + 'SECOND_MICROSECOND', + 'SELECT', + 'SENSITIVE', + 'SEPARATOR', + 'SET', + 'SHOW', + 'SIGNAL', + 'SMALLINT', + 'SPATIAL', + 'SPECIFIC', + 'SQL', + 'SQL_BIG_RESULT', + 'SQL_CALC_FOUND_ROWS', + 'SQL_SMALL_RESULT', + 'SQLEXCEPTION', + 'SQLSTATE', + 'SQLWARNING', + 'SSL', + 'STARTING', + 'STRAIGHT_JOIN', + 'TABLE', + 'TERMINATED', + 'THEN', + 'TINYBLOB', + 'TINYINT', + 'TINYTEXT', + 'TO', + 'TRAILING', + 'TRIGGER', + 'TRUE', + 'UNDO', + 'UNION', + 'UNIQUE', + 'UNLOCK', + 'UNSIGNED', + 'UPDATE', + 'USAGE', + 'USE', + 'USING', + 'UTC_DATE', + 'UTC_TIME', + 'UTC_TIMESTAMP', + 'VALUES', + 'VARBINARY', + 'VARCHAR', + 'VARCHARACTER', + 'VARYING', + 'WHEN', + 'WHERE', + 'WHILE', + 'WITH', + 'WRITE', + 'XOR', + 'YEAR_MONTH', + 'ZEROFILL', + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQLKeywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQLKeywords.php new file mode 100644 index 00000000..ba257741 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQLKeywords.php @@ -0,0 +1,273 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * MySQL Keywordlist. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author David Coallier + */ +class MySQLKeywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'MySQL'; + } + + /** + * {@inheritdoc} + */ + protected function getKeywords() + { + return array( + 'ADD', + 'ALL', + 'ALTER', + 'ANALYZE', + 'AND', + 'AS', + 'ASC', + 'ASENSITIVE', + 'BEFORE', + 'BETWEEN', + 'BIGINT', + 'BINARY', + 'BLOB', + 'BOTH', + 'BY', + 'CALL', + 'CASCADE', + 'CASE', + 'CHANGE', + 'CHAR', + 'CHARACTER', + 'CHECK', + 'COLLATE', + 'COLUMN', + 'CONDITION', + 'CONNECTION', + 'CONSTRAINT', + 'CONTINUE', + 'CONVERT', + 'CREATE', + 'CROSS', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'CURSOR', + 'DATABASE', + 'DATABASES', + 'DAY_HOUR', + 'DAY_MICROSECOND', + 'DAY_MINUTE', + 'DAY_SECOND', + 'DEC', + 'DECIMAL', + 'DECLARE', + 'DEFAULT', + 'DELAYED', + 'DELETE', + 'DESC', + 'DESCRIBE', + 'DETERMINISTIC', + 'DISTINCT', + 'DISTINCTROW', + 'DIV', + 'DOUBLE', + 'DROP', + 'DUAL', + 'EACH', + 'ELSE', + 'ELSEIF', + 'ENCLOSED', + 'ESCAPED', + 'EXISTS', + 'EXIT', + 'EXPLAIN', + 'FALSE', + 'FETCH', + 'FLOAT', + 'FLOAT4', + 'FLOAT8', + 'FOR', + 'FORCE', + 'FOREIGN', + 'FROM', + 'FULLTEXT', + 'GOTO', + 'GRANT', + 'GROUP', + 'HAVING', + 'HIGH_PRIORITY', + 'HOUR_MICROSECOND', + 'HOUR_MINUTE', + 'HOUR_SECOND', + 'IF', + 'IGNORE', + 'IN', + 'INDEX', + 'INFILE', + 'INNER', + 'INOUT', + 'INSENSITIVE', + 'INSERT', + 'INT', + 'INT1', + 'INT2', + 'INT3', + 'INT4', + 'INT8', + 'INTEGER', + 'INTERVAL', + 'INTO', + 'IS', + 'ITERATE', + 'JOIN', + 'KEY', + 'KEYS', + 'KILL', + 'LABEL', + 'LEADING', + 'LEAVE', + 'LEFT', + 'LIKE', + 'LIMIT', + 'LINES', + 'LOAD', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'LOCK', + 'LONG', + 'LONGBLOB', + 'LONGTEXT', + 'LOOP', + 'LOW_PRIORITY', + 'MATCH', + 'MEDIUMBLOB', + 'MEDIUMINT', + 'MEDIUMTEXT', + 'MIDDLEINT', + 'MINUTE_MICROSECOND', + 'MINUTE_SECOND', + 'MOD', + 'MODIFIES', + 'NATURAL', + 'NOT', + 'NO_WRITE_TO_BINLOG', + 'NULL', + 'NUMERIC', + 'ON', + 'OPTIMIZE', + 'OPTION', + 'OPTIONALLY', + 'OR', + 'ORDER', + 'OUT', + 'OUTER', + 'OUTFILE', + 'PRECISION', + 'PRIMARY', + 'PROCEDURE', + 'PURGE', + 'RAID0', + 'RANGE', + 'READ', + 'READS', + 'REAL', + 'REFERENCES', + 'REGEXP', + 'RELEASE', + 'RENAME', + 'REPEAT', + 'REPLACE', + 'REQUIRE', + 'RESTRICT', + 'RETURN', + 'REVOKE', + 'RIGHT', + 'RLIKE', + 'SCHEMA', + 'SCHEMAS', + 'SECOND_MICROSECOND', + 'SELECT', + 'SENSITIVE', + 'SEPARATOR', + 'SET', + 'SHOW', + 'SMALLINT', + 'SONAME', + 'SPATIAL', + 'SPECIFIC', + 'SQL', + 'SQLEXCEPTION', + 'SQLSTATE', + 'SQLWARNING', + 'SQL_BIG_RESULT', + 'SQL_CALC_FOUND_ROWS', + 'SQL_SMALL_RESULT', + 'SSL', + 'STARTING', + 'STRAIGHT_JOIN', + 'TABLE', + 'TERMINATED', + 'THEN', + 'TINYBLOB', + 'TINYINT', + 'TINYTEXT', + 'TO', + 'TRAILING', + 'TRIGGER', + 'TRUE', + 'UNDO', + 'UNION', + 'UNIQUE', + 'UNLOCK', + 'UNSIGNED', + 'UPDATE', + 'USAGE', + 'USE', + 'USING', + 'UTC_DATE', + 'UTC_TIME', + 'UTC_TIMESTAMP', + 'VALUES', + 'VARBINARY', + 'VARCHAR', + 'VARCHARACTER', + 'VARYING', + 'WHEN', + 'WHERE', + 'WHILE', + 'WITH', + 'WRITE', + 'X509', + 'XOR', + 'YEAR_MONTH', + 'ZEROFILL', + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/OracleKeywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/OracleKeywords.php new file mode 100644 index 00000000..b37fdad3 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/OracleKeywords.php @@ -0,0 +1,161 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * Oracle Keywordlist. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author David Coallier + */ +class OracleKeywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'Oracle'; + } + + /** + * {@inheritdoc} + */ + protected function getKeywords() + { + return array( + 'ACCESS', + 'ELSE', + 'MODIFY', + 'START', + 'ADD', + 'EXCLUSIVE', + 'NOAUDIT', + 'SELECT', + 'ALL', + 'EXISTS', + 'NOCOMPRESS', + 'SESSION', + 'ALTER', + 'FILE', + 'NOT', + 'SET', + 'AND', + 'FLOAT', + 'NOTFOUND ', + 'SHARE', + 'ANY', + 'FOR', + 'NOWAIT', + 'SIZE', + 'ARRAYLEN', + 'FROM', + 'NULL', + 'SMALLINT', + 'AS', + 'GRANT', + 'NUMBER', + 'SQLBUF', + 'ASC', + 'GROUP', + 'OF', + 'SUCCESSFUL', + 'AUDIT', + 'HAVING', + 'OFFLINE ', + 'SYNONYM', + 'BETWEEN', + 'IDENTIFIED', + 'ON', + 'SYSDATE', + 'BY', + 'IMMEDIATE', + 'ONLINE', + 'TABLE', + 'CHAR', + 'IN', + 'OPTION', + 'THEN', + 'CHECK', + 'INCREMENT', + 'OR', + 'TO', + 'CLUSTER', + 'INDEX', + 'ORDER', + 'TRIGGER', + 'COLUMN', + 'INITIAL', + 'PCTFREE', + 'UID', + 'COMMENT', + 'INSERT', + 'PRIOR', + 'UNION', + 'COMPRESS', + 'INTEGER', + 'PRIVILEGES', + 'UNIQUE', + 'CONNECT', + 'INTERSECT', + 'PUBLIC', + 'UPDATE', + 'CREATE', + 'INTO', + 'RAW', + 'USER', + 'CURRENT', + 'IS', + 'RENAME', + 'VALIDATE', + 'DATE', + 'LEVEL', + 'RESOURCE', + 'VALUES', + 'DECIMAL', + 'LIKE', + 'REVOKE', + 'VARCHAR', + 'DEFAULT', + 'LOCK', + 'ROW', + 'VARCHAR2', + 'DELETE', + 'LONG', + 'ROWID', + 'VIEW', + 'DESC', + 'MAXEXTENTS', + 'ROWLABEL', + 'WHENEVER', + 'DISTINCT', + 'MINUS', + 'ROWNUM', + 'WHERE', + 'DROP', + 'MODE', + 'ROWS', + 'WITH', + 'RANGE', + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL91Keywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL91Keywords.php new file mode 100644 index 00000000..4e5c632d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL91Keywords.php @@ -0,0 +1,148 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * PostgreSQL 9.1 reserved keywords list. + * + * @author Martin Hasoň + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class PostgreSQL91Keywords extends PostgreSQLKeywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'PostgreSQL91'; + } + + /** + * {@inheritdoc} + * + * @link http://www.postgresql.org/docs/9.1/static/sql-keywords-appendix.html + */ + protected function getKeywords() + { + return array( + 'ALL', + 'ANALYSE', + 'ANALYZE', + 'AND', + 'ANY', + 'ARRAY', + 'AS', + 'ASC', + 'ASYMMETRIC', + 'AUTHORIZATION', + 'BINARY', + 'BOTH', + 'CASE', + 'CAST', + 'CHECK', + 'COLLATE', + 'COLUMN', + 'CONCURRENTLY', + 'CONSTRAINT', + 'CREATE', + 'CROSS', + 'CURRENT_CATALOG', + 'CURRENT_DATE', + 'CURRENT_ROLE', + 'CURRENT_SCHEMA', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'DEFAULT', + 'DEFERRABLE', + 'DESC', + 'DISTINCT', + 'DO', + 'ELSE', + 'END', + 'EXCEPT', + 'FALSE', + 'FETCH', + 'FOR', + 'FOREIGN', + 'FREEZE', + 'FROM', + 'FULL', + 'GRANT', + 'GROUP', + 'HAVING', + 'ILIKE', + 'IN', + 'INITIALLY', + 'INNER', + 'INTERSECT', + 'INTO', + 'IS', + 'ISNULL', + 'JOIN', + 'LEADING', + 'LEFT', + 'LIKE', + 'LIMIT', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'NATURAL', + 'NOT', + 'NOTNULL', + 'NULL', + 'OFFSET', + 'ON', + 'ONLY', + 'OR', + 'ORDER', + 'OUTER', + 'OVER', + 'OVERLAPS', + 'PLACING', + 'PRIMARY', + 'REFERENCES', + 'RETURNING', + 'RIGHT', + 'SELECT', + 'SESSION_USER', + 'SIMILAR', + 'SOME', + 'SYMMETRIC', + 'TABLE', + 'THEN', + 'TO', + 'TRAILING', + 'TRUE', + 'UNION', + 'UNIQUE', + 'USER', + 'USING', + 'VARIADIC', + 'VERBOSE', + 'WHEN', + 'WHERE', + 'WINDOW', + 'WITH', + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL92Keywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL92Keywords.php new file mode 100644 index 00000000..7180dfd9 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL92Keywords.php @@ -0,0 +1,50 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * PostgreSQL 9.2 reserved keywords list. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class PostgreSQL92Keywords extends PostgreSQL91Keywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'PostgreSQL92'; + } + + /** + * {@inheritdoc} + * + * @link http://www.postgresql.org/docs/9.2/static/sql-keywords-appendix.html + */ + protected function getKeywords() + { + return array_merge(parent::getKeywords(), array( + 'COLLATION', + )); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQLKeywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQLKeywords.php new file mode 100644 index 00000000..9e49d7f2 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQLKeywords.php @@ -0,0 +1,135 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * PostgreSQL Keywordlist. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Marcelo Santos Araujo + */ +class PostgreSQLKeywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'PostgreSQL'; + } + + /** + * {@inheritdoc} + */ + protected function getKeywords() + { + return array( + 'ALL', + 'ANALYSE', + 'ANALYZE', + 'AND', + 'ANY', + 'AS', + 'ASC', + 'AUTHORIZATION', + 'BETWEEN', + 'BINARY', + 'BOTH', + 'CASE', + 'CAST', + 'CHECK', + 'COLLATE', + 'COLUMN', + 'CONSTRAINT', + 'CREATE', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'DEFAULT', + 'DEFERRABLE', + 'DESC', + 'DISTINCT', + 'DO', + 'ELSE', + 'END', + 'EXCEPT', + 'FALSE', + 'FOR', + 'FOREIGN', + 'FREEZE', + 'FROM', + 'FULL', + 'GRANT', + 'GROUP', + 'HAVING', + 'ILIKE', + 'IN', + 'INITIALLY', + 'INNER', + 'INTERSECT', + 'INTO', + 'IS', + 'ISNULL', + 'JOIN', + 'LEADING', + 'LEFT', + 'LIKE', + 'LIMIT', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'NATURAL', + 'NEW', + 'NOT', + 'NOTNULL', + 'NULL', + 'OFF', + 'OFFSET', + 'OLD', + 'ON', + 'ONLY', + 'OR', + 'ORDER', + 'OUTER', + 'OVERLAPS', + 'PLACING', + 'PRIMARY', + 'REFERENCES', + 'SELECT', + 'SESSION_USER', + 'SIMILAR', + 'SOME', + 'TABLE', + 'THEN', + 'TO', + 'TRAILING', + 'TRUE', + 'UNION', + 'UNIQUE', + 'USER', + 'USING', + 'VERBOSE', + 'WHEN', + 'WHERE' + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/ReservedKeywordsValidator.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/ReservedKeywordsValidator.php new file mode 100644 index 00000000..e1fdd492 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/ReservedKeywordsValidator.php @@ -0,0 +1,143 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +use Doctrine\DBAL\Schema\Visitor\Visitor; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\Index; + +class ReservedKeywordsValidator implements Visitor +{ + /** + * @var KeywordList[] + */ + private $keywordLists = array(); + + /** + * @var array + */ + private $violations = array(); + + /** + * @param \Doctrine\DBAL\Platforms\Keywords\KeywordList[] $keywordLists + */ + public function __construct(array $keywordLists) + { + $this->keywordLists = $keywordLists; + } + + /** + * @return array + */ + public function getViolations() + { + return $this->violations; + } + + /** + * @param string $word + * + * @return array + */ + private function isReservedWord($word) + { + if ($word[0] == "`") { + $word = str_replace('`', '', $word); + } + + $keywordLists = array(); + foreach ($this->keywordLists as $keywordList) { + if ($keywordList->isKeyword($word)) { + $keywordLists[] = $keywordList->getName(); + } + } + + return $keywordLists; + } + + /** + * @param string $asset + * @param array $violatedPlatforms + * + * @return void + */ + private function addViolation($asset, $violatedPlatforms) + { + if ( ! $violatedPlatforms) { + return; + } + + $this->violations[] = $asset . ' keyword violations: ' . implode(', ', $violatedPlatforms); + } + + /** + * {@inheritdoc} + */ + public function acceptColumn(Table $table, Column $column) + { + $this->addViolation( + 'Table ' . $table->getName() . ' column ' . $column->getName(), + $this->isReservedWord($column->getName()) + ); + } + + /** + * {@inheritdoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + } + + /** + * {@inheritdoc} + */ + public function acceptIndex(Table $table, Index $index) + { + } + + /** + * {@inheritdoc} + */ + public function acceptSchema(Schema $schema) + { + } + + /** + * {@inheritdoc} + */ + public function acceptSequence(Sequence $sequence) + { + } + + /** + * {@inheritdoc} + */ + public function acceptTable(Table $table) + { + $this->addViolation( + 'Table ' . $table->getName(), + $this->isReservedWord($table->getName()) + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere11Keywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere11Keywords.php new file mode 100644 index 00000000..12eacbf9 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere11Keywords.php @@ -0,0 +1,55 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * SAP Sybase SQL Anywhere 11 reserved keywords list. + * + * @author Steve Müller + */ +class SQLAnywhere11Keywords extends SQLAnywhereKeywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLAnywhere11'; + } + + /** + * {@inheritdoc} + * + * @link http://dcx.sybase.com/1100/en/dbreference_en11/alhakeywords.html + */ + protected function getKeywords() + { + return array_merge( + array_diff( + parent::getKeywords(), + array('IQ') + ), + array( + 'MERGE', + 'OPENSTRING' + ) + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere12Keywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere12Keywords.php new file mode 100644 index 00000000..22e04893 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere12Keywords.php @@ -0,0 +1,64 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * SAP Sybase SQL Anywhere 12 reserved keywords list. + * + * @author Steve Müller + */ +class SQLAnywhere12Keywords extends SQLAnywhere11Keywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLAnywhere12'; + } + + /** + * {@inheritdoc} + * + * @link http://dcx.sybase.com/1200/en/dbreference/alhakeywords.html + */ + protected function getKeywords() + { + return array_merge( + array_diff( + parent::getKeywords(), + array( + 'INDEX_LPAREN', + 'SYNTAX_ERROR', + 'WITH_CUBE', + 'WITH_LPAREN', + 'WITH_ROLLUP' + ) + ), + array( + 'DATETIMEOFFSET', + 'LIMIT', + 'OPENXML', + 'SPATIAL', + 'TREAT' + ) + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere16Keywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere16Keywords.php new file mode 100644 index 00000000..4d78b841 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere16Keywords.php @@ -0,0 +1,56 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * SAP Sybase SQL Anywhere 16 reserved keywords list. + * + * @author Steve Müller + */ +class SQLAnywhere16Keywords extends SQLAnywhere12Keywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLAnywhere16'; + } + + /** + * {@inheritdoc} + * + * @link http://dcx.sybase.com/index.html#sa160/en/dbreference/alhakeywords.html + */ + protected function getKeywords() + { + return array_merge( + parent::getKeywords(), + array( + 'ARRAY', + 'JSON', + 'ROW', + 'ROWTYPE', + 'UNNEST', + 'VARRAY' + ) + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhereKeywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhereKeywords.php new file mode 100644 index 00000000..cabf1e44 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhereKeywords.php @@ -0,0 +1,281 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * SAP Sybase SQL Anywhere 10 reserved keywords list. + * + * @author Steve Müller + */ +class SQLAnywhereKeywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLAnywhere'; + } + + /** + * {@inheritdoc} + * + * @link http://infocenter.sybase.com/help/topic/com.sybase.dbrfen10/pdf/dbrfen10.pdf?noframes=true + */ + protected function getKeywords() + { + return array( + 'ADD', + 'ALL', + 'ALTER', + 'AND', + 'ANY', + 'AS', + 'ASC', + 'ATTACH', + 'BACKUP', + 'BEGIN', + 'BETWEEN', + 'BIGINT', + 'BINARY', + 'BIT', + 'BOTTOM', + 'BREAK', + 'BY', + 'CALL', + 'CAPABILITY', + 'CASCADE', + 'CASE', + 'CAST', + 'CHAR', + 'CHAR_CONVERT', + 'CHARACTER', + 'CHECK', + 'CHECKPOINT', + 'CLOSE', + 'COMMENT', + 'COMMIT', + 'COMPRESSED', + 'CONFLICT', + 'CONNECT', + 'CONSTRAINT', + 'CONTAINS', + 'CONTINUE', + 'CONVERT', + 'CREATE', + 'CROSS', + 'CUBE', + 'CURRENT', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'CURSOR', + 'DATE', + 'DBSPACE', + 'DEALLOCATE', + 'DEC', + 'DECIMAL', + 'DECLARE', + 'DEFAULT', + 'DELETE', + 'DELETING', + 'DESC', + 'DETACH', + 'DISTINCT', + 'DO', + 'DOUBLE', + 'DROP', + 'DYNAMIC', + 'ELSE', + 'ELSEIF', + 'ENCRYPTED', + 'END', + 'ENDIF', + 'ESCAPE', + 'EXCEPT', + 'EXCEPTION', + 'EXEC', + 'EXECUTE', + 'EXISTING', + 'EXISTS', + 'EXTERNLOGIN', + 'FETCH', + 'FIRST', + 'FLOAT', + 'FOR', + 'FORCE', + 'FOREIGN', + 'FORWARD', + 'FROM', + 'FULL', + 'GOTO', + 'GRANT', + 'GROUP', + 'HAVING', + 'HOLDLOCK', + 'IDENTIFIED', + 'IF', + 'IN', + 'INDEX', + 'INDEX_LPAREN', + 'INNER', + 'INOUT', + 'INSENSITIVE', + 'INSERT', + 'INSERTING', + 'INSTALL', + 'INSTEAD', + 'INT', + 'INTEGER', + 'INTEGRATED', + 'INTERSECT', + 'INTO', + 'IQ', + 'IS', + 'ISOLATION', + 'JOIN', + 'KERBEROS', + 'KEY', + 'LATERAL', + 'LEFT', + 'LIKE', + 'LOCK', + 'LOGIN', + 'LONG', + 'MATCH', + 'MEMBERSHIP', + 'MESSAGE', + 'MODE', + 'MODIFY', + 'NATURAL', + 'NCHAR', + 'NEW', + 'NO', + 'NOHOLDLOCK', + 'NOT', + 'NOTIFY', + 'NULL', + 'NUMERIC', + 'NVARCHAR', + 'OF', + 'OFF', + 'ON', + 'OPEN', + 'OPTION', + 'OPTIONS', + 'OR', + 'ORDER', + 'OTHERS', + 'OUT', + 'OUTER', + 'OVER', + 'PASSTHROUGH', + 'PRECISION', + 'PREPARE', + 'PRIMARY', + 'PRINT', + 'PRIVILEGES', + 'PROC', + 'PROCEDURE', + 'PUBLICATION', + 'RAISERROR', + 'READTEXT', + 'REAL', + 'REFERENCE', + 'REFERENCES', + 'REFRESH', + 'RELEASE', + 'REMOTE', + 'REMOVE', + 'RENAME', + 'REORGANIZE', + 'RESOURCE', + 'RESTORE', + 'RESTRICT', + 'RETURN', + 'REVOKE', + 'RIGHT', + 'ROLLBACK', + 'ROLLUP', + 'SAVE', + 'SAVEPOINT', + 'SCROLL', + 'SELECT', + 'SENSITIVE', + 'SESSION', + 'SET', + 'SETUSER', + 'SHARE', + 'SMALLINT', + 'SOME', + 'SQLCODE', + 'SQLSTATE', + 'START', + 'STOP', + 'SUBTRANS', + 'SUBTRANSACTION', + 'SYNCHRONIZE', + 'SYNTAX_ERROR', + 'TABLE', + 'TEMPORARY', + 'THEN', + 'TIME', + 'TIMESTAMP', + 'TINYINT', + 'TO', + 'TOP', + 'TRAN', + 'TRIGGER', + 'TRUNCATE', + 'TSEQUAL', + 'UNBOUNDED', + 'UNION', + 'UNIQUE', + 'UNIQUEIDENTIFIER', + 'UNKNOWN', + 'UNSIGNED', + 'UPDATE', + 'UPDATING', + 'USER', + 'USING', + 'VALIDATE', + 'VALUES', + 'VARBINARY', + 'VARBIT', + 'VARCHAR', + 'VARIABLE', + 'VARYING', + 'VIEW', + 'WAIT', + 'WAITFOR', + 'WHEN', + 'WHERE', + 'WHILE', + 'WINDOW', + 'WITH', + 'WITH_CUBE', + 'WITH_LPAREN', + 'WITH_ROLLUP', + 'WITHIN', + 'WORK', + 'WRITETEXT', + 'XML' + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2005Keywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2005Keywords.php new file mode 100644 index 00000000..3a743491 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2005Keywords.php @@ -0,0 +1,56 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * Microsoft SQL Server 2005 reserved keyword dictionary. + * + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link www.doctrine-project.com + * @since 2.3 + * @author Steve Müller + */ +class SQLServer2005Keywords extends SQLServerKeywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLServer2005'; + } + + /** + * {@inheritdoc} + * + * @link http://msdn.microsoft.com/en-US/library/ms189822%28v=sql.90%29.aspx + */ + protected function getKeywords() + { + return array_merge(array_diff(parent::getKeywords(), array('DUMMY')), array( + 'EXTERNAL', + 'PIVOT', + 'REVERT', + 'SECURITYAUDIT', + 'TABLESAMPLE', + 'UNPIVOT' + )); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2008Keywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2008Keywords.php new file mode 100644 index 00000000..38556b50 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2008Keywords.php @@ -0,0 +1,51 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * Microsoft SQL Server 2008 reserved keyword dictionary. + * + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link www.doctrine-project.com + * @since 2.3 + * @author Steve Müller + */ +class SQLServer2008Keywords extends SQLServer2005Keywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLServer2008'; + } + + /** + * {@inheritdoc} + * + * @link http://msdn.microsoft.com/en-us/library/ms189822%28v=sql.100%29.aspx + */ + protected function getKeywords() + { + return array_merge(parent::getKeywords(), array( + 'MERGE' + )); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2012Keywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2012Keywords.php new file mode 100644 index 00000000..ada1c026 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2012Keywords.php @@ -0,0 +1,55 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * Microsoft SQL Server 2012 reserved keyword dictionary. + * + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link www.doctrine-project.com + * @since 2.3 + * @author Steve Müller + */ +class SQLServer2012Keywords extends SQLServer2008Keywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLServer2012'; + } + + /** + * {@inheritdoc} + * + * @link http://msdn.microsoft.com/en-us/library/ms189822.aspx + */ + protected function getKeywords() + { + return array_merge(parent::getKeywords(), array( + 'SEMANTICKEYPHRASETABLE', + 'SEMANTICSIMILARITYDETAILSTABLE', + 'SEMANTICSIMILARITYTABLE', + 'TRY_CONVERT', + 'WITHIN GROUP' + )); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServerKeywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServerKeywords.php new file mode 100644 index 00000000..fb43d2d5 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServerKeywords.php @@ -0,0 +1,231 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * Microsoft SQL Server 2000 reserved keyword dictionary. + * + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link www.doctrine-project.com + * @since 2.0 + * @author Benjamin Eberlei + * @author David Coallier + * @author Steve Müller + */ +class SQLServerKeywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLServer'; + } + + /** + * {@inheritdoc} + * + * @link http://msdn.microsoft.com/en-us/library/aa238507%28v=sql.80%29.aspx + */ + protected function getKeywords() + { + return array( + 'ADD', + 'ALL', + 'ALTER', + 'AND', + 'ANY', + 'AS', + 'ASC', + 'AUTHORIZATION', + 'BACKUP', + 'BEGIN', + 'BETWEEN', + 'BREAK', + 'BROWSE', + 'BULK', + 'BY', + 'CASCADE', + 'CASE', + 'CHECK', + 'CHECKPOINT', + 'CLOSE', + 'CLUSTERED', + 'COALESCE', + 'COLLATE', + 'COLUMN', + 'COMMIT', + 'COMPUTE', + 'CONSTRAINT', + 'CONTAINS', + 'CONTAINSTABLE', + 'CONTINUE', + 'CONVERT', + 'CREATE', + 'CROSS', + 'CURRENT', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'CURSOR', + 'DATABASE', + 'DBCC', + 'DEALLOCATE', + 'DECLARE', + 'DEFAULT', + 'DELETE', + 'DENY', + 'DESC', + 'DISK', + 'DISTINCT', + 'DISTRIBUTED', + 'DOUBLE', + 'DROP', + 'DUMP', + 'ELSE', + 'END', + 'ERRLVL', + 'ESCAPE', + 'EXCEPT', + 'EXEC', + 'EXECUTE', + 'EXISTS', + 'EXIT', + 'EXTERNAL', + 'FETCH', + 'FILE', + 'FILLFACTOR', + 'FOR', + 'FOREIGN', + 'FREETEXT', + 'FREETEXTTABLE', + 'FROM', + 'FULL', + 'FUNCTION', + 'GOTO', + 'GRANT', + 'GROUP', + 'HAVING', + 'HOLDLOCK', + 'IDENTITY', + 'IDENTITY_INSERT', + 'IDENTITYCOL', + 'IF', + 'IN', + 'INDEX', + 'INNER', + 'INSERT', + 'INTERSECT', + 'INTO', + 'IS', + 'JOIN', + 'KEY', + 'KILL', + 'LEFT', + 'LIKE', + 'LINENO', + 'LOAD', + 'NATIONAL', + 'NOCHECK ', + 'NONCLUSTERED', + 'NOT', + 'NULL', + 'NULLIF', + 'OF', + 'OFF', + 'OFFSETS', + 'ON', + 'OPEN', + 'OPENDATASOURCE', + 'OPENQUERY', + 'OPENROWSET', + 'OPENXML', + 'OPTION', + 'OR', + 'ORDER', + 'OUTER', + 'OVER', + 'PERCENT', + 'PIVOT', + 'PLAN', + 'PRECISION', + 'PRIMARY', + 'PRINT', + 'PROC', + 'PROCEDURE', + 'PUBLIC', + 'RAISERROR', + 'READ', + 'READTEXT', + 'RECONFIGURE', + 'REFERENCES', + 'REPLICATION', + 'RESTORE', + 'RESTRICT', + 'RETURN', + 'REVERT', + 'REVOKE', + 'RIGHT', + 'ROLLBACK', + 'ROWCOUNT', + 'ROWGUIDCOL', + 'RULE', + 'SAVE', + 'SCHEMA', + 'SECURITYAUDIT', + 'SELECT', + 'SESSION_USER', + 'SET', + 'SETUSER', + 'SHUTDOWN', + 'SOME', + 'STATISTICS', + 'SYSTEM_USER', + 'TABLE', + 'TABLESAMPLE', + 'TEXTSIZE', + 'THEN', + 'TO', + 'TOP', + 'TRAN', + 'TRANSACTION', + 'TRIGGER', + 'TRUNCATE', + 'TSEQUAL', + 'UNION', + 'UNIQUE', + 'UNPIVOT', + 'UPDATE', + 'UPDATETEXT', + 'USE', + 'USER', + 'VALUES', + 'VARYING', + 'VIEW', + 'WAITFOR', + 'WHEN', + 'WHERE', + 'WHILE', + 'WITH', + 'WRITETEXT' + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLiteKeywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLiteKeywords.php new file mode 100644 index 00000000..a162d3d5 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLiteKeywords.php @@ -0,0 +1,168 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * SQLite Keywordlist. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class SQLiteKeywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLite'; + } + + /** + * {@inheritdoc} + */ + protected function getKeywords() + { + return array( + 'ABORT', + 'ACTION', + 'ADD', + 'AFTER', + 'ALL', + 'ALTER', + 'ANALYZE', + 'AND', + 'AS', + 'ASC', + 'ATTACH', + 'AUTOINCREMENT', + 'BEFORE', + 'BEGIN', + 'BETWEEN', + 'BY', + 'CASCADE', + 'CASE', + 'CAST', + 'CHECK', + 'COLLATE', + 'COLUMN', + 'COMMIT', + 'CONFLICT', + 'CONSTRAINT', + 'CREATE', + 'CROSS', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'DATABASE', + 'DEFAULT', + 'DEFERRABLE', + 'DEFERRED', + 'DELETE', + 'DESC', + 'DETACH', + 'DISTINCT', + 'DROP', + 'EACH', + 'ELSE', + 'END', + 'ESCAPE', + 'EXCEPT', + 'EXCLUSIVE', + 'EXISTS', + 'EXPLAIN', + 'FAIL', + 'FOR', + 'FOREIGN', + 'FROM', + 'FULL', + 'GLOB', + 'GROUP', + 'HAVING', + 'IF', + 'IGNORE', + 'IMMEDIATE', + 'IN', + 'INDEX', + 'INDEXED', + 'INITIALLY', + 'INNER', + 'INSERT', + 'INSTEAD', + 'INTERSECT', + 'INTO', + 'IS', + 'ISNULL', + 'JOIN', + 'KEY', + 'LEFT', + 'LIKE', + 'LIMIT', + 'MATCH', + 'NATURAL', + 'NO', + 'NOT', + 'NOTNULL', + 'NULL', + 'OF', + 'OFFSET', + 'ON', + 'OR', + 'ORDER', + 'OUTER', + 'PLAN', + 'PRAGMA', + 'PRIMARY', + 'QUERY', + 'RAISE', + 'REFERENCES', + 'REGEXP', + 'REINDEX', + 'RELEASE', + 'RENAME', + 'REPLACE', + 'RESTRICT', + 'RIGHT', + 'ROLLBACK', + 'ROW', + 'SAVEPOINT', + 'SELECT', + 'SET', + 'TABLE', + 'TEMP', + 'TEMPORARY', + 'THEN', + 'TO', + 'TRANSACTION', + 'TRIGGER', + 'UNION', + 'UNIQUE', + 'UPDATE', + 'USING', + 'VACUUM', + 'VALUES', + 'VIEW', + 'VIRTUAL', + 'WHEN', + 'WHERE' + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySQL57Platform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySQL57Platform.php new file mode 100644 index 00000000..36b525e2 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySQL57Platform.php @@ -0,0 +1,67 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\TableDiff; + +/** + * Provides the behavior, features and SQL dialect of the MySQL 5.7 database platform. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class MySQL57Platform extends MySqlPlatform +{ + /** + * {@inheritdoc} + */ + protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff) + { + return array(); + } + + /** + * {@inheritdoc} + */ + protected function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff) + { + return array(); + } + + /** + * {@inheritdoc} + */ + protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) + { + return array( + 'ALTER TABLE ' . $tableName . ' RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this) + ); + } + + /** + * {@inheritdoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\MySQL57Keywords'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php new file mode 100644 index 00000000..15d9df39 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php @@ -0,0 +1,1093 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\Schema\Identifier; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Types\BlobType; +use Doctrine\DBAL\Types\TextType; + +/** + * The MySqlPlatform provides the behavior, features and SQL dialect of the + * MySQL database platform. This platform represents a MySQL 5.0 or greater platform that + * uses the InnoDB storage engine. + * + * @since 2.0 + * @author Roman Borschel + * @author Benjamin Eberlei + * @todo Rename: MySQLPlatform + */ +class MySqlPlatform extends AbstractPlatform +{ + const LENGTH_LIMIT_TINYTEXT = 255; + const LENGTH_LIMIT_TEXT = 65535; + const LENGTH_LIMIT_MEDIUMTEXT = 16777215; + + const LENGTH_LIMIT_TINYBLOB = 255; + const LENGTH_LIMIT_BLOB = 65535; + const LENGTH_LIMIT_MEDIUMBLOB = 16777215; + + /** + * Adds MySQL-specific LIMIT clause to the query + * 18446744073709551615 is 2^64-1 maximum of unsigned BIGINT the biggest limit possible + */ + protected function doModifyLimitQuery($query, $limit, $offset) + { + if ($limit !== null) { + $query .= ' LIMIT ' . $limit; + if ($offset !== null) { + $query .= ' OFFSET ' . $offset; + } + } elseif ($offset !== null) { + $query .= ' LIMIT 18446744073709551615 OFFSET ' . $offset; + } + + return $query; + } + + /** + * {@inheritDoc} + */ + public function getIdentifierQuoteCharacter() + { + return '`'; + } + + /** + * {@inheritDoc} + */ + public function getRegexpExpression() + { + return 'RLIKE'; + } + + /** + * {@inheritDoc} + */ + public function getGuidExpression() + { + return 'UUID()'; + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'LOCATE(' . $substr . ', ' . $str . ')'; + } + + return 'LOCATE(' . $substr . ', ' . $str . ', '.$startPos.')'; + } + + /** + * {@inheritDoc} + */ + public function getConcatExpression() + { + $args = func_get_args(); + + return 'CONCAT(' . join(', ', (array) $args) . ')'; + } + + /** + * {@inheritdoc} + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + $function = '+' === $operator ? 'DATE_ADD' : 'DATE_SUB'; + + return $function . '(' . $date . ', INTERVAL ' . $interval . ' ' . $unit . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return 'DATEDIFF(' . $date1 . ', ' . $date2 . ')'; + } + + /** + * {@inheritDoc} + */ + public function getListDatabasesSQL() + { + return 'SHOW DATABASES'; + } + + /** + * {@inheritDoc} + */ + public function getListTableConstraintsSQL($table) + { + return 'SHOW INDEX FROM ' . $table; + } + + /** + * {@inheritDoc} + * + * Two approaches to listing the table indexes. The information_schema is + * preferred, because it doesn't cause problems with SQL keywords such as "order" or "table". + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + if ($currentDatabase) { + $currentDatabase = $this->quoteStringLiteral($currentDatabase); + $table = $this->quoteStringLiteral($table); + + return "SELECT TABLE_NAME AS `Table`, NON_UNIQUE AS Non_Unique, INDEX_NAME AS Key_name, ". + "SEQ_IN_INDEX AS Seq_in_index, COLUMN_NAME AS Column_Name, COLLATION AS Collation, ". + "CARDINALITY AS Cardinality, SUB_PART AS Sub_Part, PACKED AS Packed, " . + "NULLABLE AS `Null`, INDEX_TYPE AS Index_Type, COMMENT AS Comment " . + "FROM information_schema.STATISTICS WHERE TABLE_NAME = " . $table . " AND TABLE_SCHEMA = " . $currentDatabase; + } + + return 'SHOW INDEX FROM ' . $table; + } + + /** + * {@inheritDoc} + */ + public function getListViewsSQL($database) + { + $database = $this->quoteStringLiteral($database); + + return "SELECT * FROM information_schema.VIEWS WHERE TABLE_SCHEMA = " . $database; + } + + /** + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table, $database = null) + { + $table = $this->quoteStringLiteral($table); + + if (null !== $database) { + $database = $this->quoteStringLiteral($database); + } + + $sql = "SELECT DISTINCT k.`CONSTRAINT_NAME`, k.`COLUMN_NAME`, k.`REFERENCED_TABLE_NAME`, ". + "k.`REFERENCED_COLUMN_NAME` /*!50116 , c.update_rule, c.delete_rule */ ". + "FROM information_schema.key_column_usage k /*!50116 ". + "INNER JOIN information_schema.referential_constraints c ON ". + " c.constraint_name = k.constraint_name AND ". + " c.table_name = $table */ WHERE k.table_name = $table"; + + $databaseNameSql = null === $database ? 'DATABASE()' : $database; + + $sql .= " AND k.table_schema = $databaseNameSql /*!50116 AND c.constraint_schema = $databaseNameSql */"; + $sql .= " AND k.`REFERENCED_COLUMN_NAME` is not NULL"; + + return $sql; + } + + /** + * {@inheritDoc} + */ + public function getCreateViewSQL($name, $sql) + { + return 'CREATE VIEW ' . $name . ' AS ' . $sql; + } + + /** + * {@inheritDoc} + */ + public function getDropViewSQL($name) + { + return 'DROP VIEW '. $name; + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') + : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); + } + + /** + * {@inheritdoc} + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? 'BINARY(' . ($length ?: 255) . ')' : 'VARBINARY(' . ($length ?: 255) . ')'; + } + + /** + * Gets the SQL snippet used to declare a CLOB column type. + * TINYTEXT : 2 ^ 8 - 1 = 255 + * TEXT : 2 ^ 16 - 1 = 65535 + * MEDIUMTEXT : 2 ^ 24 - 1 = 16777215 + * LONGTEXT : 2 ^ 32 - 1 = 4294967295 + * + * @param array $field + * + * @return string + */ + public function getClobTypeDeclarationSQL(array $field) + { + if ( ! empty($field['length']) && is_numeric($field['length'])) { + $length = $field['length']; + + if ($length <= static::LENGTH_LIMIT_TINYTEXT) { + return 'TINYTEXT'; + } + + if ($length <= static::LENGTH_LIMIT_TEXT) { + return 'TEXT'; + } + + if ($length <= static::LENGTH_LIMIT_MEDIUMTEXT) { + return 'MEDIUMTEXT'; + } + } + + return 'LONGTEXT'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) { + return 'TIMESTAMP'; + } + + return 'DATETIME'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIME'; + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $field) + { + return 'TINYINT(1)'; + } + + /** + * Obtain DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration to be used in statements like CREATE TABLE. + * + * @deprecated Deprecated since version 2.5, Use {@link self::getColumnCollationDeclarationSQL()} instead. + * + * @param string $collation name of the collation + * + * @return string DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration. + */ + public function getCollationFieldDeclaration($collation) + { + return $this->getColumnCollationDeclarationSQL($collation); + } + + /** + * {@inheritDoc} + * + * MySql prefers "autoincrement" identity columns since sequences can only + * be emulated with a table. + */ + public function prefersIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + * + * MySql supports this through AUTO_INCREMENT columns. + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsInlineColumnComments() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsColumnCollation() + { + return true; + } + + public function getListTablesSQL() + { + return "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"; + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + $table = $this->quoteStringLiteral($table); + + if ($database) { + $database = $this->quoteStringLiteral($database); + } else { + $database = 'DATABASE()'; + } + + return "SELECT COLUMN_NAME AS Field, COLUMN_TYPE AS Type, IS_NULLABLE AS `Null`, ". + "COLUMN_KEY AS `Key`, COLUMN_DEFAULT AS `Default`, EXTRA AS Extra, COLUMN_COMMENT AS Comment, " . + "CHARACTER_SET_NAME AS CharacterSet, COLLATION_NAME AS Collation ". + "FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = " . $database . " AND TABLE_NAME = " . $table; + } + + /** + * {@inheritDoc} + */ + public function getCreateDatabaseSQL($name) + { + return 'CREATE DATABASE ' . $name; + } + + /** + * {@inheritDoc} + */ + public function getDropDatabaseSQL($name) + { + return 'DROP DATABASE ' . $name; + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) + { + $queryFields = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + foreach ($options['uniqueConstraints'] as $index => $definition) { + $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition); + } + } + + // add all indexes + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach ($options['indexes'] as $index => $definition) { + $queryFields .= ', ' . $this->getIndexDeclarationSQL($index, $definition); + } + } + + // attach all primary keys + if (isset($options['primary']) && ! empty($options['primary'])) { + $keyColumns = array_unique(array_values($options['primary'])); + $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; + } + + $query = 'CREATE '; + + if (!empty($options['temporary'])) { + $query .= 'TEMPORARY '; + } + + $query .= 'TABLE ' . $tableName . ' (' . $queryFields . ') '; + $query .= $this->buildTableOptions($options); + $query .= $this->buildPartitionOptions($options); + + $sql[] = $query; + $engine = 'INNODB'; + + if (isset($options['engine'])) { + $engine = strtoupper(trim($options['engine'])); + } + + // Propagate foreign key constraints only for InnoDB. + if (isset($options['foreignKeys']) && $engine === 'INNODB') { + foreach ((array) $options['foreignKeys'] as $definition) { + $sql[] = $this->getCreateForeignKeySQL($definition, $tableName); + } + } + + return $sql; + } + + /** + * {@inheritdoc} + */ + public function getDefaultValueDeclarationSQL($field) + { + // Unset the default value if the given field definition does not allow default values. + if ($field['type'] instanceof TextType || $field['type'] instanceof BlobType) { + $field['default'] = null; + } + + return parent::getDefaultValueDeclarationSQL($field); + } + + /** + * Build SQL for table options + * + * @param array $options + * + * @return string + */ + private function buildTableOptions(array $options) + { + if (isset($options['table_options'])) { + return $options['table_options']; + } + + $tableOptions = array(); + + // Charset + if ( ! isset($options['charset'])) { + $options['charset'] = 'utf8'; + } + + $tableOptions[] = sprintf('DEFAULT CHARACTER SET %s', $options['charset']); + + // Collate + if ( ! isset($options['collate'])) { + $options['collate'] = 'utf8_unicode_ci'; + } + + $tableOptions[] = sprintf('COLLATE %s', $options['collate']); + + // Engine + if ( ! isset($options['engine'])) { + $options['engine'] = 'InnoDB'; + } + + $tableOptions[] = sprintf('ENGINE = %s', $options['engine']); + + // Auto increment + if (isset($options['auto_increment'])) { + $tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']); + } + + // Comment + if (isset($options['comment'])) { + $comment = trim($options['comment'], " '"); + + $tableOptions[] = sprintf("COMMENT = %s ", $this->quoteStringLiteral($comment)); + } + + // Row format + if (isset($options['row_format'])) { + $tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']); + } + + return implode(' ', $tableOptions); + } + + /** + * Build SQL for partition options. + * + * @param array $options + * + * @return string + */ + private function buildPartitionOptions(array $options) + { + return (isset($options['partition_options'])) + ? ' ' . $options['partition_options'] + : ''; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $columnSql = array(); + $queryParts = array(); + if ($diff->newName !== false) { + $queryParts[] = 'RENAME TO ' . $diff->getNewName()->getQuotedName($this); + } + + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $columnArray = $column->toArray(); + $columnArray['comment'] = $this->getColumnComment($column); + $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); + } + + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $queryParts[] = 'DROP ' . $column->getQuotedName($this); + } + + foreach ($diff->changedColumns as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + /* @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */ + $column = $columnDiff->column; + $columnArray = $column->toArray(); + + // Don't propagate default value changes for unsupported column types. + if ($columnDiff->hasChanged('default') && + count($columnDiff->changedProperties) === 1 && + ($columnArray['type'] instanceof TextType || $columnArray['type'] instanceof BlobType) + ) { + continue; + } + + $columnArray['comment'] = $this->getColumnComment($column); + $queryParts[] = 'CHANGE ' . ($columnDiff->getOldColumnName()->getQuotedName($this)) . ' ' + . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $oldColumnName = new Identifier($oldColumnName); + $columnArray = $column->toArray(); + $columnArray['comment'] = $this->getColumnComment($column); + $queryParts[] = 'CHANGE ' . $oldColumnName->getQuotedName($this) . ' ' + . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); + } + + if (isset($diff->addedIndexes['primary'])) { + $keyColumns = array_unique(array_values($diff->addedIndexes['primary']->getColumns())); + $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')'; + unset($diff->addedIndexes['primary']); + } + + $sql = array(); + $tableSql = array(); + + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + if (count($queryParts) > 0) { + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . implode(", ", $queryParts); + } + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff) + ); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * {@inheritDoc} + */ + protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) + { + $sql = array(); + $table = $diff->getName($this)->getQuotedName($this); + + foreach ($diff->changedIndexes as $changedIndex) { + $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $changedIndex)); + } + + foreach ($diff->removedIndexes as $remKey => $remIndex) { + $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $remIndex)); + + foreach ($diff->addedIndexes as $addKey => $addIndex) { + if ($remIndex->getColumns() == $addIndex->getColumns()) { + + $indexClause = 'INDEX ' . $addIndex->getName(); + + if ($addIndex->isPrimary()) { + $indexClause = 'PRIMARY KEY'; + } elseif ($addIndex->isUnique()) { + $indexClause = 'UNIQUE INDEX ' . $addIndex->getName(); + } + + $query = 'ALTER TABLE ' . $table . ' DROP INDEX ' . $remIndex->getName() . ', '; + $query .= 'ADD ' . $indexClause; + $query .= ' (' . $this->getIndexFieldDeclarationListSQL($addIndex->getQuotedColumns($this)) . ')'; + + $sql[] = $query; + + unset($diff->removedIndexes[$remKey]); + unset($diff->addedIndexes[$addKey]); + + break; + } + } + } + + $engine = 'INNODB'; + + if ($diff->fromTable instanceof Table && $diff->fromTable->hasOption('engine')) { + $engine = strtoupper(trim($diff->fromTable->getOption('engine'))); + } + + // Suppress foreign key constraint propagation on non-supporting engines. + if ('INNODB' !== $engine) { + $diff->addedForeignKeys = array(); + $diff->changedForeignKeys = array(); + $diff->removedForeignKeys = array(); + } + + $sql = array_merge( + $sql, + $this->getPreAlterTableAlterIndexForeignKeySQL($diff), + parent::getPreAlterTableIndexForeignKeySQL($diff), + $this->getPreAlterTableRenameIndexForeignKeySQL($diff) + ); + + return $sql; + } + + /** + * @param TableDiff $diff + * @param Index $index + * + * @return string[] + */ + private function getPreAlterTableAlterPrimaryKeySQL(TableDiff $diff, Index $index) + { + $sql = array(); + + if (! $index->isPrimary() || ! $diff->fromTable instanceof Table) { + return $sql; + } + + $tableName = $diff->getName($this)->getQuotedName($this); + + // Dropping primary keys requires to unset autoincrement attribute on the particular column first. + foreach ($index->getColumns() as $columnName) { + $column = $diff->fromTable->getColumn($columnName); + + if ($column->getAutoincrement() === true) { + $column->setAutoincrement(false); + + $sql[] = 'ALTER TABLE ' . $tableName . ' MODIFY ' . + $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + + // original autoincrement information might be needed later on by other parts of the table alteration + $column->setAutoincrement(true); + } + } + + return $sql; + } + + /** + * @param TableDiff $diff The table diff to gather the SQL for. + * + * @return array + */ + private function getPreAlterTableAlterIndexForeignKeySQL(TableDiff $diff) + { + $sql = array(); + $table = $diff->getName($this)->getQuotedName($this); + + foreach ($diff->changedIndexes as $changedIndex) { + // Changed primary key + if ($changedIndex->isPrimary() && $diff->fromTable instanceof Table) { + foreach ($diff->fromTable->getPrimaryKeyColumns() as $columnName) { + $column = $diff->fromTable->getColumn($columnName); + + // Check if an autoincrement column was dropped from the primary key. + if ($column->getAutoincrement() && ! in_array($columnName, $changedIndex->getColumns())) { + // The autoincrement attribute needs to be removed from the dropped column + // before we can drop and recreate the primary key. + $column->setAutoincrement(false); + + $sql[] = 'ALTER TABLE ' . $table . ' MODIFY ' . + $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + + // Restore the autoincrement attribute as it might be needed later on + // by other parts of the table alteration. + $column->setAutoincrement(true); + } + } + } + } + + return $sql; + } + + /** + * @param TableDiff $diff The table diff to gather the SQL for. + * + * @return array + */ + protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff) + { + $sql = array(); + $tableName = $diff->getName($this)->getQuotedName($this); + + foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) { + if (! in_array($foreignKey, $diff->changedForeignKeys, true)) { + $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName); + } + } + + return $sql; + } + + /** + * Returns the remaining foreign key constraints that require one of the renamed indexes. + * + * "Remaining" here refers to the diff between the foreign keys currently defined in the associated + * table and the foreign keys to be removed. + * + * @param TableDiff $diff The table diff to evaluate. + * + * @return array + */ + private function getRemainingForeignKeyConstraintsRequiringRenamedIndexes(TableDiff $diff) + { + if (empty($diff->renamedIndexes) || ! $diff->fromTable instanceof Table) { + return array(); + } + + $foreignKeys = array(); + /** @var \Doctrine\DBAL\Schema\ForeignKeyConstraint[] $remainingForeignKeys */ + $remainingForeignKeys = array_diff_key( + $diff->fromTable->getForeignKeys(), + $diff->removedForeignKeys + ); + + foreach ($remainingForeignKeys as $foreignKey) { + foreach ($diff->renamedIndexes as $index) { + if ($foreignKey->intersectsIndexColumns($index)) { + $foreignKeys[] = $foreignKey; + + break; + } + } + } + + return $foreignKeys; + } + + /** + * {@inheritdoc} + */ + protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) + { + return array_merge( + parent::getPostAlterTableIndexForeignKeySQL($diff), + $this->getPostAlterTableRenameIndexForeignKeySQL($diff) + ); + } + + /** + * @param TableDiff $diff The table diff to gather the SQL for. + * + * @return array + */ + protected function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff) + { + $sql = array(); + $tableName = (false !== $diff->newName) + ? $diff->getNewName()->getQuotedName($this) + : $diff->getName($this)->getQuotedName($this); + + foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) { + if (! in_array($foreignKey, $diff->changedForeignKeys, true)) { + $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName); + } + } + + return $sql; + } + + /** + * {@inheritDoc} + */ + protected function getCreateIndexSQLFlags(Index $index) + { + $type = ''; + if ($index->isUnique()) { + $type .= 'UNIQUE '; + } elseif ($index->hasFlag('fulltext')) { + $type .= 'FULLTEXT '; + } elseif ($index->hasFlag('spatial')) { + $type .= 'SPATIAL '; + } + + return $type; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $field) + { + return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $field) + { + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $field) + { + return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + $autoinc = ''; + if ( ! empty($columnDef['autoincrement'])) { + $autoinc = ' AUTO_INCREMENT'; + } + $unsigned = (isset($columnDef['unsigned']) && $columnDef['unsigned']) ? ' UNSIGNED' : ''; + + return $unsigned . $autoinc; + } + + /** + * {@inheritDoc} + */ + public function getAdvancedForeignKeyOptionsSQL(\Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey) + { + $query = ''; + if ($foreignKey->hasOption('match')) { + $query .= ' MATCH ' . $foreignKey->getOption('match'); + } + $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); + + return $query; + } + + /** + * {@inheritDoc} + */ + public function getDropIndexSQL($index, $table=null) + { + if ($index instanceof Index) { + $indexName = $index->getQuotedName($this); + } elseif (is_string($index)) { + $indexName = $index; + } else { + throw new \InvalidArgumentException('MysqlPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.'); + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } elseif (!is_string($table)) { + throw new \InvalidArgumentException('MysqlPlatform::getDropIndexSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); + } + + if ($index instanceof Index && $index->isPrimary()) { + // mysql primary keys are always named "PRIMARY", + // so we cannot use them in statements because of them being keyword. + return $this->getDropPrimaryKeySQL($table); + } + + return 'DROP INDEX ' . $indexName . ' ON ' . $table; + } + + /** + * @param string $table + * + * @return string + */ + protected function getDropPrimaryKeySQL($table) + { + return 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY'; + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'mysql'; + } + + /** + * {@inheritDoc} + */ + public function getReadLockSQL() + { + return 'LOCK IN SHARE MODE'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'tinyint' => 'boolean', + 'smallint' => 'smallint', + 'mediumint' => 'integer', + 'int' => 'integer', + 'integer' => 'integer', + 'bigint' => 'bigint', + 'tinytext' => 'text', + 'mediumtext' => 'text', + 'longtext' => 'text', + 'text' => 'text', + 'varchar' => 'string', + 'string' => 'string', + 'char' => 'string', + 'date' => 'date', + 'datetime' => 'datetime', + 'timestamp' => 'datetime', + 'time' => 'time', + 'float' => 'float', + 'double' => 'float', + 'real' => 'float', + 'decimal' => 'decimal', + 'numeric' => 'decimal', + 'year' => 'date', + 'longblob' => 'blob', + 'blob' => 'blob', + 'mediumblob' => 'blob', + 'tinyblob' => 'blob', + 'binary' => 'binary', + 'varbinary' => 'binary', + 'set' => 'simple_array', + ); + } + + /** + * {@inheritDoc} + */ + public function getVarcharMaxLength() + { + return 65535; + } + + /** + * {@inheritdoc} + */ + public function getBinaryMaxLength() + { + return 65535; + } + + /** + * {@inheritDoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\MySQLKeywords'; + } + + /** + * {@inheritDoc} + * + * MySQL commits a transaction implicitly when DROP TABLE is executed, however not + * if DROP TEMPORARY TABLE is executed. + */ + public function getDropTemporaryTableSQL($table) + { + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } elseif (!is_string($table)) { + throw new \InvalidArgumentException('getDropTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); + } + + return 'DROP TEMPORARY TABLE ' . $table; + } + + /** + * Gets the SQL Snippet used to declare a BLOB column type. + * TINYBLOB : 2 ^ 8 - 1 = 255 + * BLOB : 2 ^ 16 - 1 = 65535 + * MEDIUMBLOB : 2 ^ 24 - 1 = 16777215 + * LONGBLOB : 2 ^ 32 - 1 = 4294967295 + * + * @param array $field + * + * @return string + */ + public function getBlobTypeDeclarationSQL(array $field) + { + if ( ! empty($field['length']) && is_numeric($field['length'])) { + $length = $field['length']; + + if ($length <= static::LENGTH_LIMIT_TINYBLOB) { + return 'TINYBLOB'; + } + + if ($length <= static::LENGTH_LIMIT_BLOB) { + return 'BLOB'; + } + + if ($length <= static::LENGTH_LIMIT_MEDIUMBLOB) { + return 'MEDIUMBLOB'; + } + } + + return 'LONGBLOB'; + } + + /** + * {@inheritdoc} + */ + public function quoteStringLiteral($str) + { + $str = str_replace('\\', '\\\\', $str); // MySQL requires backslashes to be escaped aswell. + + return parent::quoteStringLiteral($str); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/OraclePlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/OraclePlatform.php new file mode 100644 index 00000000..3cc5413f --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/OraclePlatform.php @@ -0,0 +1,1176 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Identifier; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Types\BinaryType; + +/** + * OraclePlatform. + * + * @since 2.0 + * @author Roman Borschel + * @author Lukas Smith (PEAR MDB2 library) + * @author Benjamin Eberlei + */ +class OraclePlatform extends AbstractPlatform +{ + /** + * Assertion for Oracle identifiers. + * + * @link http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm + * + * @param string $identifier + * + * @throws DBALException + */ + static public function assertValidIdentifier($identifier) + { + if ( ! preg_match('(^(([a-zA-Z]{1}[a-zA-Z0-9_$#]{0,})|("[^"]+"))$)', $identifier)) { + throw new DBALException("Invalid Oracle identifier"); + } + } + + /** + * {@inheritDoc} + */ + public function getSubstringExpression($value, $position, $length = null) + { + if ($length !== null) { + return "SUBSTR($value, $position, $length)"; + } + + return "SUBSTR($value, $position)"; + } + + /** + * {@inheritDoc} + */ + public function getNowExpression($type = 'timestamp') + { + switch ($type) { + case 'date': + case 'time': + case 'timestamp': + default: + return 'TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')'; + } + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'INSTR('.$str.', '.$substr.')'; + } + + return 'INSTR('.$str.', '.$substr.', '.$startPos.')'; + } + + /** + * {@inheritDoc} + */ + public function getGuidExpression() + { + return 'SYS_GUID()'; + } + + /** + * {@inheritdoc} + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + switch ($unit) { + case self::DATE_INTERVAL_UNIT_MONTH: + case self::DATE_INTERVAL_UNIT_QUARTER: + case self::DATE_INTERVAL_UNIT_YEAR: + switch ($unit) { + case self::DATE_INTERVAL_UNIT_QUARTER: + $interval *= 3; + break; + + case self::DATE_INTERVAL_UNIT_YEAR: + $interval *= 12; + break; + } + + return 'ADD_MONTHS(' . $date . ', ' . $operator . $interval . ')'; + + default: + $calculationClause = ''; + + switch ($unit) { + case self::DATE_INTERVAL_UNIT_SECOND: + $calculationClause = '/24/60/60'; + break; + + case self::DATE_INTERVAL_UNIT_MINUTE: + $calculationClause = '/24/60'; + break; + + case self::DATE_INTERVAL_UNIT_HOUR: + $calculationClause = '/24'; + break; + + case self::DATE_INTERVAL_UNIT_WEEK: + $calculationClause = '*7'; + break; + } + + return '(' . $date . $operator . $interval . $calculationClause . ')'; + } + } + + /** + * {@inheritDoc} + * + * Note: Since Oracle timestamp differences are calculated down to the microsecond we have to truncate + * them to the difference in days. This is obviously a restriction of the original functionality, but we + * need to make this a portable function. + */ + public function getDateDiffExpression($date1, $date2) + { + return "TRUNC(TO_NUMBER(SUBSTR((" . $date1 . "-" . $date2 . "), 1, INSTR(" . $date1 . "-" . $date2 .", ' '))))"; + } + + /** + * {@inheritDoc} + */ + public function getBitAndComparisonExpression($value1, $value2) + { + return 'BITAND('.$value1 . ', ' . $value2 . ')'; + } + + /** + * {@inheritDoc} + */ + public function getBitOrComparisonExpression($value1, $value2) + { + return '(' . $value1 . '-' . + $this->getBitAndComparisonExpression($value1, $value2) + . '+' . $value2 . ')'; + } + + /** + * {@inheritDoc} + * + * Need to specifiy minvalue, since start with is hidden in the system and MINVALUE <= START WITH. + * Therefore we can use MINVALUE to be able to get a hint what START WITH was for later introspection + * in {@see listSequences()} + */ + public function getCreateSequenceSQL(Sequence $sequence) + { + return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . + ' START WITH ' . $sequence->getInitialValue() . + ' MINVALUE ' . $sequence->getInitialValue() . + ' INCREMENT BY ' . $sequence->getAllocationSize() . + $this->getSequenceCacheSQL($sequence); + } + + /** + * {@inheritDoc} + */ + public function getAlterSequenceSQL(Sequence $sequence) + { + return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize() + . $this->getSequenceCacheSQL($sequence); + } + + /** + * Cache definition for sequences + * + * @param Sequence $sequence + * + * @return string + */ + private function getSequenceCacheSQL(Sequence $sequence) + { + if ($sequence->getCache() === 0) { + return ' NOCACHE'; + } else if ($sequence->getCache() === 1) { + return ' NOCACHE'; + } else if ($sequence->getCache() > 1) { + return ' CACHE ' . $sequence->getCache(); + } + + return ''; + } + + /** + * {@inheritDoc} + */ + public function getSequenceNextValSQL($sequenceName) + { + return 'SELECT ' . $sequenceName . '.nextval FROM DUAL'; + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + */ + protected function _getTransactionIsolationLevelSQL($level) + { + switch ($level) { + case \Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED: + return 'READ UNCOMMITTED'; + case \Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED: + return 'READ COMMITTED'; + case \Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ: + case \Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE: + return 'SERIALIZABLE'; + default: + return parent::_getTransactionIsolationLevelSQL($level); + } + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $field) + { + return 'NUMBER(1)'; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $field) + { + return 'NUMBER(10)'; + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $field) + { + return 'NUMBER(20)'; + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $field) + { + return 'NUMBER(5)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIMESTAMP(0)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIMESTAMP(0) WITH TIME ZONE'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + return ''; + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(2000)') + : ($length ? 'VARCHAR2(' . $length . ')' : 'VARCHAR2(4000)'); + } + + /** + * {@inheritdoc} + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) + { + return 'RAW(' . ($length ?: $this->getBinaryMaxLength()) . ')'; + } + + /** + * {@inheritdoc} + */ + public function getBinaryMaxLength() + { + return 2000; + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + return 'CLOB'; + } + + /** + * {@inheritDoc} + */ + public function getListDatabasesSQL() + { + return 'SELECT username FROM all_users'; + } + + /** + * {@inheritDoc} + */ + public function getListSequencesSQL($database) + { + $database = $this->normalizeIdentifier($database); + $database = $this->quoteStringLiteral($database->getName()); + + return "SELECT sequence_name, min_value, increment_by FROM sys.all_sequences ". + "WHERE SEQUENCE_OWNER = " . $database; + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($table, array $columns, array $options = array()) + { + $indexes = isset($options['indexes']) ? $options['indexes'] : array(); + $options['indexes'] = array(); + $sql = parent::_getCreateTableSQL($table, $columns, $options); + + foreach ($columns as $name => $column) { + if (isset($column['sequence'])) { + $sql[] = $this->getCreateSequenceSQL($column['sequence'], 1); + } + + if (isset($column['autoincrement']) && $column['autoincrement'] || + (isset($column['autoinc']) && $column['autoinc'])) { + $sql = array_merge($sql, $this->getCreateAutoincrementSql($name, $table)); + } + } + + if (isset($indexes) && ! empty($indexes)) { + foreach ($indexes as $index) { + $sql[] = $this->getCreateIndexSQL($index, $table); + } + } + + return $sql; + } + + /** + * {@inheritDoc} + * + * @license New BSD License + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + $table = $this->normalizeIdentifier($table); + $table = $this->quoteStringLiteral($table->getName()); + + return "SELECT uind_col.index_name AS name, + ( + SELECT uind.index_type + FROM user_indexes uind + WHERE uind.index_name = uind_col.index_name + ) AS type, + decode( + ( + SELECT uind.uniqueness + FROM user_indexes uind + WHERE uind.index_name = uind_col.index_name + ), + 'NONUNIQUE', + 0, + 'UNIQUE', + 1 + ) AS is_unique, + uind_col.column_name AS column_name, + uind_col.column_position AS column_pos, + ( + SELECT ucon.constraint_type + FROM user_constraints ucon + WHERE ucon.constraint_name = uind_col.index_name + ) AS is_primary + FROM user_ind_columns uind_col + WHERE uind_col.table_name = " . $table . " + ORDER BY uind_col.column_position ASC"; + } + + /** + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return 'SELECT * FROM sys.user_tables'; + } + + /** + * {@inheritDoc} + */ + public function getListViewsSQL($database) + { + return 'SELECT view_name, text FROM sys.user_views'; + } + + /** + * {@inheritDoc} + */ + public function getCreateViewSQL($name, $sql) + { + return 'CREATE VIEW ' . $name . ' AS ' . $sql; + } + + /** + * {@inheritDoc} + */ + public function getDropViewSQL($name) + { + return 'DROP VIEW '. $name; + } + + /** + * @param string $name + * @param string $table + * @param integer $start + * + * @return array + */ + public function getCreateAutoincrementSql($name, $table, $start = 1) + { + $tableIdentifier = $this->normalizeIdentifier($table); + $quotedTableName = $tableIdentifier->getQuotedName($this); + $unquotedTableName = $tableIdentifier->getName(); + + $nameIdentifier = $this->normalizeIdentifier($name); + $quotedName = $nameIdentifier->getQuotedName($this); + $unquotedName = $nameIdentifier->getName(); + + $sql = array(); + + $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($tableIdentifier); + + $idx = new Index($autoincrementIdentifierName, array($quotedName), true, true); + + $sql[] = 'DECLARE + constraints_Count NUMBER; +BEGIN + SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = \'' . $unquotedTableName . '\' AND CONSTRAINT_TYPE = \'P\'; + IF constraints_Count = 0 OR constraints_Count = \'\' THEN + EXECUTE IMMEDIATE \''.$this->getCreateConstraintSQL($idx, $quotedTableName).'\'; + END IF; +END;'; + + $sequenceName = $this->getIdentitySequenceName( + $tableIdentifier->isQuoted() ? $quotedTableName : $unquotedTableName, + $nameIdentifier->isQuoted() ? $quotedName : $unquotedName + ); + $sequence = new Sequence($sequenceName, $start); + $sql[] = $this->getCreateSequenceSQL($sequence); + + $sql[] = 'CREATE TRIGGER ' . $autoincrementIdentifierName . ' + BEFORE INSERT + ON ' . $quotedTableName . ' + FOR EACH ROW +DECLARE + last_Sequence NUMBER; + last_InsertID NUMBER; +BEGIN + SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL; + IF (:NEW.' . $quotedName . ' IS NULL OR :NEW.'.$quotedName.' = 0) THEN + SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL; + ELSE + SELECT NVL(Last_Number, 0) INTO last_Sequence + FROM User_Sequences + WHERE Sequence_Name = \'' . $sequence->getName() . '\'; + SELECT :NEW.' . $quotedName . ' INTO last_InsertID FROM DUAL; + WHILE (last_InsertID > last_Sequence) LOOP + SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL; + END LOOP; + END IF; +END;'; + + return $sql; + } + + /** + * Returns the SQL statements to drop the autoincrement for the given table name. + * + * @param string $table The table name to drop the autoincrement for. + * + * @return array + */ + public function getDropAutoincrementSql($table) + { + $table = $this->normalizeIdentifier($table); + $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($table); + $identitySequenceName = $this->getIdentitySequenceName( + $table->isQuoted() ? $table->getQuotedName($this) : $table->getName(), + '' + ); + + return array( + 'DROP TRIGGER ' . $autoincrementIdentifierName, + $this->getDropSequenceSQL($identitySequenceName), + $this->getDropConstraintSQL($autoincrementIdentifierName, $table->getQuotedName($this)), + ); + } + + /** + * Normalizes the given identifier. + * + * Uppercases the given identifier if it is not quoted by intention + * to reflect Oracle's internal auto uppercasing strategy of unquoted identifiers. + * + * @param string $name The identifier to normalize. + * + * @return Identifier The normalized identifier. + */ + private function normalizeIdentifier($name) + { + $identifier = new Identifier($name); + + return $identifier->isQuoted() ? $identifier : new Identifier(strtoupper($name)); + } + + /** + * Returns the autoincrement primary key identifier name for the given table identifier. + * + * Quotes the autoincrement primary key identifier name + * if the given table name is quoted by intention. + * + * @param Identifier $table The table identifier to return the autoincrement primary key identifier name for. + * + * @return string + */ + private function getAutoincrementIdentifierName(Identifier $table) + { + $identifierName = $table->getName() . '_AI_PK'; + + return $table->isQuoted() + ? $this->quoteSingleIdentifier($identifierName) + : $identifierName; + } + + /** + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table) + { + $table = $this->normalizeIdentifier($table); + $table = $this->quoteStringLiteral($table->getName()); + + return "SELECT alc.constraint_name, + alc.DELETE_RULE, + alc.search_condition, + cols.column_name \"local_column\", + cols.position, + r_alc.table_name \"references_table\", + r_cols.column_name \"foreign_column\" + FROM user_cons_columns cols +LEFT JOIN user_constraints alc + ON alc.constraint_name = cols.constraint_name +LEFT JOIN user_constraints r_alc + ON alc.r_constraint_name = r_alc.constraint_name +LEFT JOIN user_cons_columns r_cols + ON r_alc.constraint_name = r_cols.constraint_name + AND cols.position = r_cols.position + WHERE alc.constraint_name = cols.constraint_name + AND alc.constraint_type = 'R' + AND alc.table_name = " . $table . " + ORDER BY cols.constraint_name ASC, cols.position ASC"; + } + + /** + * {@inheritDoc} + */ + public function getListTableConstraintsSQL($table) + { + $table = $this->normalizeIdentifier($table); + $table = $this->quoteStringLiteral($table->getName()); + + return "SELECT * FROM user_constraints WHERE table_name = " . $table; + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + $table = $this->normalizeIdentifier($table); + $table = $this->quoteStringLiteral($table->getName()); + + $tabColumnsTableName = "user_tab_columns"; + $colCommentsTableName = "user_col_comments"; + $tabColumnsOwnerCondition = ''; + $colCommentsOwnerCondition = ''; + + if (null !== $database && '/' !== $database) { + $database = $this->normalizeIdentifier($database); + $database = $this->quoteStringLiteral($database->getName()); + $tabColumnsTableName = "all_tab_columns"; + $colCommentsTableName = "all_col_comments"; + $tabColumnsOwnerCondition = "AND c.owner = " . $database; + $colCommentsOwnerCondition = "AND d.OWNER = c.OWNER"; + } + + return "SELECT c.*, + ( + SELECT d.comments + FROM $colCommentsTableName d + WHERE d.TABLE_NAME = c.TABLE_NAME " . $colCommentsOwnerCondition . " + AND d.COLUMN_NAME = c.COLUMN_NAME + ) AS comments + FROM $tabColumnsTableName c + WHERE c.table_name = " . $table . " $tabColumnsOwnerCondition + ORDER BY c.column_name"; + } + + /** + * {@inheritDoc} + */ + public function getDropSequenceSQL($sequence) + { + if ($sequence instanceof Sequence) { + $sequence = $sequence->getQuotedName($this); + } + + return 'DROP SEQUENCE ' . $sequence; + } + + /** + * {@inheritDoc} + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + if (! $foreignKey instanceof ForeignKeyConstraint) { + $foreignKey = new Identifier($foreignKey); + } + + if (! $table instanceof Table) { + $table = new Identifier($table); + } + + $foreignKey = $foreignKey->getQuotedName($this); + $table = $table->getQuotedName($this); + + return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey; + } + + /** + * {@inheritdoc} + */ + public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) + { + $referentialAction = null; + + if ($foreignKey->hasOption('onDelete')) { + $referentialAction = $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete')); + } + + return $referentialAction ? ' ON DELETE ' . $referentialAction : ''; + } + + /** + * {@inheritdoc} + */ + public function getForeignKeyReferentialActionSQL($action) + { + $action = strtoupper($action); + + switch ($action) { + case 'RESTRICT': // RESTRICT is not supported, therefore falling back to NO ACTION. + case 'NO ACTION': + // NO ACTION cannot be declared explicitly, + // therefore returning empty string to indicate to OMIT the referential clause. + return ''; + + case 'CASCADE': + case 'SET NULL': + return $action; + + default: + // SET DEFAULT is not supported, throw exception instead. + throw new \InvalidArgumentException('Invalid foreign key action: ' . $action); + } + } + + /** + * {@inheritDoc} + */ + public function getDropDatabaseSQL($database) + { + return 'DROP USER ' . $database . ' CASCADE'; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = array(); + $commentsSQL = array(); + $columnSql = array(); + + $fields = array(); + + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $fields[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + if ($comment = $this->getColumnComment($column)) { + $commentsSQL[] = $this->getCommentOnColumnSQL( + $diff->getName($this)->getQuotedName($this), + $column->getQuotedName($this), + $comment + ); + } + } + + if (count($fields)) { + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ADD (' . implode(', ', $fields) . ')'; + } + + $fields = array(); + foreach ($diff->changedColumns as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + /* @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */ + $column = $columnDiff->column; + + // Do not generate column alteration clause if type is binary and only fixed property has changed. + // Oracle only supports binary type columns with variable length. + // Avoids unnecessary table alteration statements. + if ($column->getType() instanceof BinaryType && + $columnDiff->hasChanged('fixed') && + count($columnDiff->changedProperties) === 1 + ) { + continue; + } + + $columnHasChangedComment = $columnDiff->hasChanged('comment'); + + /** + * Do not add query part if only comment has changed + */ + if ( ! ($columnHasChangedComment && count($columnDiff->changedProperties) === 1)) { + $columnInfo = $column->toArray(); + + if ( ! $columnDiff->hasChanged('notnull')) { + unset($columnInfo['notnull']); + } + + $fields[] = $column->getQuotedName($this) . $this->getColumnDeclarationSQL('', $columnInfo); + } + + if ($columnHasChangedComment) { + $commentsSQL[] = $this->getCommentOnColumnSQL( + $diff->getName($this)->getQuotedName($this), + $column->getQuotedName($this), + $this->getColumnComment($column) + ); + } + } + + if (count($fields)) { + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' MODIFY (' . implode(', ', $fields) . ')'; + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $oldColumnName = new Identifier($oldColumnName); + + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . + ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) .' TO ' . $column->getQuotedName($this); + } + + $fields = array(); + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $fields[] = $column->getQuotedName($this); + } + + if (count($fields)) { + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' DROP (' . implode(', ', $fields).')'; + } + + $tableSql = array(); + + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + $sql = array_merge($sql, $commentsSQL); + + if ($diff->newName !== false) { + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' RENAME TO ' . $diff->getNewName()->getQuotedName($this); + } + + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff) + ); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * {@inheritdoc} + */ + public function getColumnDeclarationSQL($name, array $field) + { + if (isset($field['columnDefinition'])) { + $columnDef = $this->getCustomTypeDeclarationSQL($field); + } else { + $default = $this->getDefaultValueDeclarationSQL($field); + + $notnull = ''; + + if (isset($field['notnull'])) { + $notnull = $field['notnull'] ? ' NOT NULL' : ' NULL'; + } + + $unique = (isset($field['unique']) && $field['unique']) ? + ' ' . $this->getUniqueFieldDeclarationSQL() : ''; + + $check = (isset($field['check']) && $field['check']) ? + ' ' . $field['check'] : ''; + + $typeDecl = $field['type']->getSqlDeclaration($field, $this); + $columnDef = $typeDecl . $default . $notnull . $unique . $check; + } + + return $name . ' ' . $columnDef; + } + + /** + * {@inheritdoc} + */ + protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) + { + if (strpos($tableName, '.') !== false) { + list($schema) = explode('.', $tableName); + $oldIndexName = $schema . '.' . $oldIndexName; + } + + return array('ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)); + } + + /** + * {@inheritDoc} + */ + public function prefersSequences() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function usesSequenceEmulatedIdentityColumns() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getIdentitySequenceName($tableName, $columnName) + { + $table = new Identifier($tableName); + + // No usage of column name to preserve BC compatibility with <2.5 + $identitySequenceName = $table->getName() . '_SEQ'; + + if ($table->isQuoted()) { + $identitySequenceName = '"' . $identitySequenceName . '"'; + } + + $identitySequenceIdentifier = $this->normalizeIdentifier($identitySequenceName); + + return $identitySequenceIdentifier->getQuotedName($this); + } + + /** + * {@inheritDoc} + */ + public function supportsCommentOnStatement() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'oracle'; + } + + /** + * {@inheritDoc} + */ + protected function doModifyLimitQuery($query, $limit, $offset = null) + { + if ($limit === null && $offset === null) { + return $query; + } + + if (preg_match('/^\s*SELECT/i', $query)) { + if (!preg_match('/\sFROM\s/i', $query)) { + $query .= " FROM dual"; + } + + $columns = array('a.*'); + + if ($offset > 0) { + $columns[] = 'ROWNUM AS doctrine_rownum'; + } + + $query = sprintf('SELECT %s FROM (%s) a', implode(', ', $columns), $query); + + if ($limit !== null) { + $query .= sprintf(' WHERE ROWNUM <= %d', $offset + $limit); + } + + if ($offset > 0) { + $query = sprintf('SELECT * FROM (%s) WHERE doctrine_rownum >= %d', $query, $offset + 1); + } + } + + return $query; + } + + /** + * {@inheritDoc} + * + * Oracle returns all column names in SQL result sets in uppercase. + */ + public function getSQLResultCasing($column) + { + return strtoupper($column); + } + + /** + * {@inheritDoc} + */ + public function getCreateTemporaryTableSnippetSQL() + { + return "CREATE GLOBAL TEMPORARY TABLE"; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzFormatString() + { + return 'Y-m-d H:i:sP'; + } + + /** + * {@inheritDoc} + */ + public function getDateFormatString() + { + return 'Y-m-d 00:00:00'; + } + + /** + * {@inheritDoc} + */ + public function getTimeFormatString() + { + return '1900-01-01 H:i:s'; + } + + /** + * {@inheritDoc} + */ + public function fixSchemaElementName($schemaElementName) + { + if (strlen($schemaElementName) > 30) { + // Trim it + return substr($schemaElementName, 0, 30); + } + + return $schemaElementName; + } + + /** + * {@inheritDoc} + */ + public function getMaxIdentifierLength() + { + return 30; + } + + /** + * {@inheritDoc} + */ + public function supportsSequences() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsForeignKeyOnUpdate() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function supportsReleaseSavepoints() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + $tableIdentifier = new Identifier($tableName); + + return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this); + } + + /** + * {@inheritDoc} + */ + public function getDummySelectSQL() + { + return 'SELECT 1 FROM DUAL'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'integer' => 'integer', + 'number' => 'integer', + 'pls_integer' => 'boolean', + 'binary_integer' => 'boolean', + 'varchar' => 'string', + 'varchar2' => 'string', + 'nvarchar2' => 'string', + 'char' => 'string', + 'nchar' => 'string', + 'date' => 'date', + 'timestamp' => 'datetime', + 'timestamptz' => 'datetimetz', + 'float' => 'float', + 'binary_float' => 'float', + 'binary_double' => 'float', + 'long' => 'string', + 'clob' => 'text', + 'nclob' => 'text', + 'raw' => 'binary', + 'long raw' => 'blob', + 'rowid' => 'string', + 'urowid' => 'string', + 'blob' => 'blob', + ); + } + + /** + * {@inheritDoc} + */ + public function releaseSavePoint($savepoint) + { + return ''; + } + + /** + * {@inheritDoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\OracleKeywords'; + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $field) + { + return 'BLOB'; + } + + /** + * {@inheritdoc} + */ + public function quoteStringLiteral($str) + { + $str = str_replace('\\', '\\\\', $str); // Oracle requires backslashes to be escaped aswell. + + return parent::quoteStringLiteral($str); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSQL91Platform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSQL91Platform.php new file mode 100644 index 00000000..b14aa730 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSQL91Platform.php @@ -0,0 +1,65 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +/** + * Provides the behavior, features and SQL dialect of the PostgreSQL 9.1 database platform. + * + * @author Martin Hasoň + * @link www.doctrine-project.org + * @since 2.5 + */ +class PostgreSQL91Platform extends PostgreSqlPlatform +{ + /** + * {@inheritDoc} + */ + public function supportsColumnCollation() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\PostgreSQL91Keywords'; + } + + /** + * {@inheritDoc} + */ + public function getColumnCollationDeclarationSQL($collation) + { + return 'COLLATE ' . $this->quoteSingleIdentifier($collation); + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + $sql = parent::getListTableColumnsSQL($table, $database); + $parts = explode('AS complete_type,', $sql, 2); + + return $parts[0].'AS complete_type, (SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation,'.$parts[1]; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSQL92Platform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSQL92Platform.php new file mode 100644 index 00000000..af1de90c --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSQL92Platform.php @@ -0,0 +1,85 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +/** + * Provides the behavior, features and SQL dialect of the PostgreSQL 9.2 database platform. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class PostgreSQL92Platform extends PostgreSQL91Platform +{ + /** + * {@inheritdoc} + */ + public function getJsonTypeDeclarationSQL(array $field) + { + return 'JSON'; + } + + /** + * {@inheritdoc} + */ + public function getSmallIntTypeDeclarationSQL(array $field) + { + if ( ! empty($field['autoincrement'])) { + return 'SMALLSERIAL'; + } + + return parent::getSmallIntTypeDeclarationSQL($field); + } + + /** + * {@inheritdoc} + */ + public function hasNativeJsonType() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\PostgreSQL92Keywords'; + } + + /** + * {@inheritdoc} + */ + protected function initializeDoctrineTypeMappings() + { + parent::initializeDoctrineTypeMappings(); + $this->doctrineTypeMapping['json'] = 'json_array'; + } + + /** + * {@inheritdoc} + */ + public function getCloseActiveDatabaseConnectionsSQL($database) + { + $database = $this->quoteStringLiteral($database); + + return "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = $database"; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php new file mode 100644 index 00000000..2f31b9d0 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php @@ -0,0 +1,1188 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ColumnDiff; +use Doctrine\DBAL\Schema\Identifier; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\Types\BinaryType; +use Doctrine\DBAL\Types\BlobType; + +/** + * PostgreSqlPlatform. + * + * @since 2.0 + * @author Roman Borschel + * @author Lukas Smith (PEAR MDB2 library) + * @author Benjamin Eberlei + * @todo Rename: PostgreSQLPlatform + */ +class PostgreSqlPlatform extends AbstractPlatform +{ + /** + * @var bool + */ + private $useBooleanTrueFalseStrings = true; + + /** + * @var array PostgreSQL booleans literals + */ + private $booleanLiterals = array( + 'true' => array( + 't', + 'true', + 'y', + 'yes', + 'on', + '1' + ), + 'false' => array( + 'f', + 'false', + 'n', + 'no', + 'off', + '0' + ) + ); + + /** + * PostgreSQL has different behavior with some drivers + * with regard to how booleans have to be handled. + * + * Enables use of 'true'/'false' or otherwise 1 and 0 instead. + * + * @param bool $flag + */ + public function setUseBooleanTrueFalseStrings($flag) + { + $this->useBooleanTrueFalseStrings = (bool) $flag; + } + + /** + * {@inheritDoc} + */ + public function getSubstringExpression($value, $from, $length = null) + { + if ($length === null) { + return 'SUBSTRING(' . $value . ' FROM ' . $from . ')'; + } + + return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $length . ')'; + } + + /** + * {@inheritDoc} + */ + public function getNowExpression() + { + return 'LOCALTIMESTAMP(0)'; + } + + /** + * {@inheritDoc} + */ + public function getRegexpExpression() + { + return 'SIMILAR TO'; + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos !== false) { + $str = $this->getSubstringExpression($str, $startPos); + + return 'CASE WHEN (POSITION('.$substr.' IN '.$str.') = 0) THEN 0 ELSE (POSITION('.$substr.' IN '.$str.') + '.($startPos-1).') END'; + } + + return 'POSITION('.$substr.' IN '.$str.')'; + } + + /** + * {@inheritdoc} + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + if (self::DATE_INTERVAL_UNIT_QUARTER === $unit) { + $interval *= 3; + $unit = self::DATE_INTERVAL_UNIT_MONTH; + } + + return "(" . $date ." " . $operator . " (" . $interval . " || ' " . $unit . "')::interval)"; + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))'; + } + + /** + * {@inheritDoc} + */ + public function supportsSequences() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsSchemas() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getDefaultSchemaName() + { + return 'public'; + } + + /** + * {@inheritDoc} + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function supportsPartialIndexes() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function usesSequenceEmulatedIdentityColumns() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getIdentitySequenceName($tableName, $columnName) + { + return $tableName . '_' . $columnName . '_seq'; + } + + /** + * {@inheritDoc} + */ + public function supportsCommentOnStatement() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function prefersSequences() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function hasNativeGuidType() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getListDatabasesSQL() + { + return 'SELECT datname FROM pg_database'; + } + + /** + * {@inheritDoc} + */ + public function getListNamespacesSQL() + { + return "SELECT schema_name AS nspname + FROM information_schema.schemata + WHERE schema_name NOT LIKE 'pg_%' + AND schema_name != 'information_schema'"; + } + + /** + * {@inheritDoc} + */ + public function getListSequencesSQL($database) + { + return "SELECT sequence_name AS relname, + sequence_schema AS schemaname + FROM information_schema.sequences + WHERE sequence_schema NOT LIKE 'pg_%' + AND sequence_schema != 'information_schema'"; + } + + /** + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return "SELECT quote_ident(table_name) AS table_name, + table_schema AS schema_name + FROM information_schema.tables + WHERE table_schema NOT LIKE 'pg_%' + AND table_schema != 'information_schema' + AND table_name != 'geometry_columns' + AND table_name != 'spatial_ref_sys' + AND table_type != 'VIEW'"; + } + + /** + * {@inheritDoc} + */ + public function getListViewsSQL($database) + { + return 'SELECT quote_ident(table_name) AS viewname, + table_schema AS schemaname, + view_definition AS definition + FROM information_schema.views + WHERE view_definition IS NOT NULL'; + } + + /** + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table, $database = null) + { + return "SELECT quote_ident(r.conname) as conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef + FROM pg_catalog.pg_constraint r + WHERE r.conrelid = + ( + SELECT c.oid + FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n + WHERE " .$this->getTableWhereClause($table) ." AND n.oid = c.relnamespace + ) + AND r.contype = 'f'"; + } + + /** + * {@inheritDoc} + */ + public function getCreateViewSQL($name, $sql) + { + return 'CREATE VIEW ' . $name . ' AS ' . $sql; + } + + /** + * {@inheritDoc} + */ + public function getDropViewSQL($name) + { + return 'DROP VIEW '. $name; + } + + /** + * {@inheritDoc} + */ + public function getListTableConstraintsSQL($table) + { + $table = new Identifier($table); + $table = $this->quoteStringLiteral($table->getName()); + + return "SELECT + quote_ident(relname) as relname + FROM + pg_class + WHERE oid IN ( + SELECT indexrelid + FROM pg_index, pg_class + WHERE pg_class.relname = $table + AND pg_class.oid = pg_index.indrelid + AND (indisunique = 't' OR indisprimary = 't') + )"; + } + + /** + * {@inheritDoc} + * + * @license New BSD License + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + return "SELECT quote_ident(relname) as relname, pg_index.indisunique, pg_index.indisprimary, + pg_index.indkey, pg_index.indrelid, + pg_get_expr(indpred, indrelid) AS where + FROM pg_class, pg_index + WHERE oid IN ( + SELECT indexrelid + FROM pg_index si, pg_class sc, pg_namespace sn + WHERE " . $this->getTableWhereClause($table, 'sc', 'sn')." AND sc.oid=si.indrelid AND sc.relnamespace = sn.oid + ) AND pg_index.indexrelid = oid"; + } + + /** + * @param string $table + * @param string $classAlias + * @param string $namespaceAlias + * + * @return string + */ + private function getTableWhereClause($table, $classAlias = 'c', $namespaceAlias = 'n') + { + $whereClause = $namespaceAlias.".nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') AND "; + if (strpos($table, ".") !== false) { + list($schema, $table) = explode(".", $table); + $schema = $this->quoteStringLiteral($schema); + } else { + $schema = "ANY(string_to_array((select replace(replace(setting,'\"\$user\"',user),' ','') from pg_catalog.pg_settings where name = 'search_path'),','))"; + } + + $table = new Identifier($table); + $table = $this->quoteStringLiteral($table->getName()); + $whereClause .= "$classAlias.relname = " . $table . " AND $namespaceAlias.nspname = $schema"; + + return $whereClause; + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + return "SELECT + a.attnum, + quote_ident(a.attname) AS field, + t.typname AS type, + format_type(a.atttypid, a.atttypmod) AS complete_type, + (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type, + (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM + pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type, + a.attnotnull AS isnotnull, + (SELECT 't' + FROM pg_index + WHERE c.oid = pg_index.indrelid + AND pg_index.indkey[0] = a.attnum + AND pg_index.indisprimary = 't' + ) AS pri, + (SELECT pg_get_expr(adbin, adrelid) + FROM pg_attrdef + WHERE c.oid = pg_attrdef.adrelid + AND pg_attrdef.adnum=a.attnum + ) AS default, + (SELECT pg_description.description + FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid + ) AS comment + FROM pg_attribute a, pg_class c, pg_type t, pg_namespace n + WHERE ".$this->getTableWhereClause($table, 'c', 'n') ." + AND a.attnum > 0 + AND a.attrelid = c.oid + AND a.atttypid = t.oid + AND n.oid = c.relnamespace + ORDER BY a.attnum"; + } + + /** + * {@inheritDoc} + */ + public function getCreateDatabaseSQL($name) + { + return 'CREATE DATABASE ' . $name; + } + + /** + * Returns the SQL statement for disallowing new connections on the given database. + * + * This is useful to force DROP DATABASE operations which could fail because of active connections. + * + * @param string $database The name of the database to disallow new connections for. + * + * @return string + */ + public function getDisallowDatabaseConnectionsSQL($database) + { + return "UPDATE pg_database SET datallowconn = 'false' WHERE datname = '$database'"; + } + + /** + * Returns the SQL statement for closing currently active connections on the given database. + * + * This is useful to force DROP DATABASE operations which could fail because of active connections. + * + * @param string $database The name of the database to close currently active connections for. + * + * @return string + */ + public function getCloseActiveDatabaseConnectionsSQL($database) + { + $database = $this->quoteStringLiteral($database); + + return "SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE datname = $database"; + } + + /** + * {@inheritDoc} + */ + public function getAdvancedForeignKeyOptionsSQL(\Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey) + { + $query = ''; + + if ($foreignKey->hasOption('match')) { + $query .= ' MATCH ' . $foreignKey->getOption('match'); + } + + $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); + + if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) { + $query .= ' DEFERRABLE'; + } else { + $query .= ' NOT DEFERRABLE'; + } + + if (($foreignKey->hasOption('feferred') && $foreignKey->getOption('feferred') !== false) + || ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) + ) { + $query .= ' INITIALLY DEFERRED'; + } else { + $query .= ' INITIALLY IMMEDIATE'; + } + + return $query; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = array(); + $commentsSQL = array(); + $columnSql = array(); + + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $query = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + + $comment = $this->getColumnComment($column); + + if (null !== $comment && '' !== $comment) { + $commentsSQL[] = $this->getCommentOnColumnSQL( + $diff->getName($this)->getQuotedName($this), + $column->getQuotedName($this), + $comment + ); + } + } + + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $query = 'DROP ' . $column->getQuotedName($this); + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + + foreach ($diff->changedColumns as $columnDiff) { + /** @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */ + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + if ($this->isUnchangedBinaryColumn($columnDiff)) { + continue; + } + + $oldColumnName = $columnDiff->getOldColumnName()->getQuotedName($this); + $column = $columnDiff->column; + + if ($columnDiff->hasChanged('type') || $columnDiff->hasChanged('precision') || $columnDiff->hasChanged('scale') || $columnDiff->hasChanged('fixed')) { + $type = $column->getType(); + + // here was a server version check before, but DBAL API does not support this anymore. + $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $type->getSqlDeclaration($column->toArray(), $this); + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + + if ($columnDiff->hasChanged('default') || $columnDiff->hasChanged('type')) { + $defaultClause = null === $column->getDefault() + ? ' DROP DEFAULT' + : ' SET' . $this->getDefaultValueDeclarationSQL($column->toArray()); + $query = 'ALTER ' . $oldColumnName . $defaultClause; + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + + if ($columnDiff->hasChanged('notnull')) { + $query = 'ALTER ' . $oldColumnName . ' ' . ($column->getNotNull() ? 'SET' : 'DROP') . ' NOT NULL'; + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + + if ($columnDiff->hasChanged('autoincrement')) { + if ($column->getAutoincrement()) { + // add autoincrement + $seqName = $this->getIdentitySequenceName($diff->name, $oldColumnName); + + $sql[] = "CREATE SEQUENCE " . $seqName; + $sql[] = "SELECT setval('" . $seqName . "', (SELECT MAX(" . $oldColumnName . ") FROM " . $diff->getName($this)->getQuotedName($this) . "))"; + $query = "ALTER " . $oldColumnName . " SET DEFAULT nextval('" . $seqName . "')"; + $sql[] = "ALTER TABLE " . $diff->getName($this)->getQuotedName($this) . " " . $query; + } else { + // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have + $query = "ALTER " . $oldColumnName . " " . "DROP DEFAULT"; + $sql[] = "ALTER TABLE " . $diff->getName($this)->getQuotedName($this) . " " . $query; + } + } + + if ($columnDiff->hasChanged('comment')) { + $commentsSQL[] = $this->getCommentOnColumnSQL( + $diff->getName($this)->getQuotedName($this), + $column->getQuotedName($this), + $this->getColumnComment($column) + ); + } + + if ($columnDiff->hasChanged('length')) { + $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $column->getType()->getSqlDeclaration($column->toArray(), $this); + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $oldColumnName = new Identifier($oldColumnName); + + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . + ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this); + } + + $tableSql = array(); + + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + $sql = array_merge($sql, $commentsSQL); + + if ($diff->newName !== false) { + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' RENAME TO ' . $diff->getNewName()->getQuotedName($this); + } + + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff) + ); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * Checks whether a given column diff is a logically unchanged binary type column. + * + * Used to determine whether a column alteration for a binary type column can be skipped. + * Doctrine's {@link \Doctrine\DBAL\Types\BinaryType} and {@link \Doctrine\DBAL\Types\BlobType} + * are mapped to the same database column type on this platform as this platform + * does not have a native VARBINARY/BINARY column type. Therefore the {@link \Doctrine\DBAL\Schema\Comparator} + * might detect differences for binary type columns which do not have to be propagated + * to database as there actually is no difference at database level. + * + * @param ColumnDiff $columnDiff The column diff to check against. + * + * @return boolean True if the given column diff is an unchanged binary type column, false otherwise. + */ + private function isUnchangedBinaryColumn(ColumnDiff $columnDiff) + { + $columnType = $columnDiff->column->getType(); + + if ( ! $columnType instanceof BinaryType && ! $columnType instanceof BlobType) { + return false; + } + + $fromColumn = $columnDiff->fromColumn instanceof Column ? $columnDiff->fromColumn : null; + + if ($fromColumn) { + $fromColumnType = $fromColumn->getType(); + + if ( ! $fromColumnType instanceof BinaryType && ! $fromColumnType instanceof BlobType) { + return false; + } + + return count(array_diff($columnDiff->changedProperties, array('type', 'length', 'fixed'))) === 0; + } + + if ($columnDiff->hasChanged('type')) { + return false; + } + + return count(array_diff($columnDiff->changedProperties, array('length', 'fixed'))) === 0; + } + + /** + * {@inheritdoc} + */ + protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) + { + if (strpos($tableName, '.') !== false) { + list($schema) = explode('.', $tableName); + $oldIndexName = $schema . '.' . $oldIndexName; + } + + return array('ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)); + } + + /** + * {@inheritdoc} + */ + public function getCommentOnColumnSQL($tableName, $columnName, $comment) + { + $tableName = new Identifier($tableName); + $columnName = new Identifier($columnName); + $comment = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment); + + return "COMMENT ON COLUMN " . $tableName->getQuotedName($this) . "." . $columnName->getQuotedName($this) . + " IS $comment"; + } + + /** + * {@inheritDoc} + */ + public function getCreateSequenceSQL(Sequence $sequence) + { + return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize() . + ' MINVALUE ' . $sequence->getInitialValue() . + ' START ' . $sequence->getInitialValue() . + $this->getSequenceCacheSQL($sequence); + } + + /** + * {@inheritDoc} + */ + public function getAlterSequenceSQL(Sequence $sequence) + { + return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize() . + $this->getSequenceCacheSQL($sequence); + } + + /** + * Cache definition for sequences + * + * @param Sequence $sequence + * + * @return string + */ + private function getSequenceCacheSQL(Sequence $sequence) + { + if ($sequence->getCache() > 1) { + return ' CACHE ' . $sequence->getCache(); + } + + return ''; + } + + /** + * {@inheritDoc} + */ + public function getDropSequenceSQL($sequence) + { + if ($sequence instanceof Sequence) { + $sequence = $sequence->getQuotedName($this); + } + + return 'DROP SEQUENCE ' . $sequence . ' CASCADE'; + } + + /** + * {@inheritDoc} + */ + public function getCreateSchemaSQL($schemaName) + { + return 'CREATE SCHEMA ' . $schemaName; + } + + /** + * {@inheritDoc} + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + return $this->getDropConstraintSQL($foreignKey, $table); + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) + { + $queryFields = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['primary']) && ! empty($options['primary'])) { + $keyColumns = array_unique(array_values($options['primary'])); + $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; + } + + $query = 'CREATE TABLE ' . $tableName . ' (' . $queryFields . ')'; + + $sql[] = $query; + + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach ($options['indexes'] as $index) { + $sql[] = $this->getCreateIndexSQL($index, $tableName); + } + } + + if (isset($options['foreignKeys'])) { + foreach ((array) $options['foreignKeys'] as $definition) { + $sql[] = $this->getCreateForeignKeySQL($definition, $tableName); + } + } + + return $sql; + } + + /** + * Converts a single boolean value. + * + * First converts the value to its native PHP boolean type + * and passes it to the given callback function to be reconverted + * into any custom representation. + * + * @param mixed $value The value to convert. + * @param callable $callback The callback function to use for converting the real boolean value. + * + * @return mixed + * @throws \UnexpectedValueException + */ + private function convertSingleBooleanValue($value, $callback) + { + if (null === $value) { + return $callback(null); + } + + if (is_bool($value) || is_numeric($value)) { + return $callback($value ? true : false); + } + + if (!is_string($value)) { + return $callback(true); + } + + /** + * Better safe than sorry: http://php.net/in_array#106319 + */ + if (in_array(trim(strtolower($value)), $this->booleanLiterals['false'], true)) { + return $callback(false); + } + + if (in_array(trim(strtolower($value)), $this->booleanLiterals['true'], true)) { + return $callback(true); + } + + throw new \UnexpectedValueException("Unrecognized boolean literal '${value}'"); + } + + /** + * Converts one or multiple boolean values. + * + * First converts the value(s) to their native PHP boolean type + * and passes them to the given callback function to be reconverted + * into any custom representation. + * + * @param mixed $item The value(s) to convert. + * @param callable $callback The callback function to use for converting the real boolean value(s). + * + * @return mixed + */ + private function doConvertBooleans($item, $callback) + { + if (is_array($item)) { + foreach ($item as $key => $value) { + $item[$key] = $this->convertSingleBooleanValue($value, $callback); + } + + return $item; + } + + return $this->convertSingleBooleanValue($item, $callback); + } + + /** + * {@inheritDoc} + * + * Postgres wants boolean values converted to the strings 'true'/'false'. + */ + public function convertBooleans($item) + { + if ( ! $this->useBooleanTrueFalseStrings) { + return parent::convertBooleans($item); + } + + return $this->doConvertBooleans( + $item, + function ($boolean) { + if (null === $boolean) { + return 'NULL'; + } + + return true === $boolean ? 'true' : 'false'; + } + ); + } + + /** + * {@inheritDoc} + */ + public function convertBooleansToDatabaseValue($item) + { + if ( ! $this->useBooleanTrueFalseStrings) { + return parent::convertBooleansToDatabaseValue($item); + } + + return $this->doConvertBooleans( + $item, + function ($boolean) { + return null === $boolean ? null : (int) $boolean; + } + ); + } + + /** + * {@inheritDoc} + */ + public function convertFromBoolean($item) + { + if (in_array(strtolower($item), $this->booleanLiterals['false'], true)) { + return false; + } + + return parent::convertFromBoolean($item); + } + + /** + * {@inheritDoc} + */ + public function getSequenceNextValSQL($sequenceName) + { + return "SELECT NEXTVAL('" . $sequenceName . "')"; + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL ' + . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $field) + { + return 'BOOLEAN'; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $field) + { + if ( ! empty($field['autoincrement'])) { + return 'SERIAL'; + } + + return 'INT'; + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $field) + { + if ( ! empty($field['autoincrement'])) { + return 'BIGSERIAL'; + } + + return 'BIGINT'; + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $field) + { + return 'SMALLINT'; + } + + /** + * {@inheritDoc} + */ + public function getGuidTypeDeclarationSQL(array $field) + { + return 'UUID'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIMESTAMP(0) WITHOUT TIME ZONE'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIMESTAMP(0) WITH TIME ZONE'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIME(0) WITHOUT TIME ZONE'; + } + + /** + * {@inheritDoc} + */ + public function getGuidExpression() + { + return 'UUID_GENERATE_V4()'; + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + return ''; + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') + : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); + } + + /** + * {@inheritdoc} + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) + { + return 'BYTEA'; + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + return 'TEXT'; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'postgresql'; + } + + /** + * {@inheritDoc} + * + * PostgreSQL returns all column names in SQL result sets in lowercase. + */ + public function getSQLResultCasing($column) + { + return strtolower($column); + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzFormatString() + { + return 'Y-m-d H:i:sO'; + } + + /** + * {@inheritDoc} + */ + public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) + { + return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)'; + } + + /** + * {@inheritDoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + $tableIdentifier = new Identifier($tableName); + $sql = 'TRUNCATE ' . $tableIdentifier->getQuotedName($this); + + if ($cascade) { + $sql .= ' CASCADE'; + } + + return $sql; + } + + /** + * {@inheritDoc} + */ + public function getReadLockSQL() + { + return 'FOR SHARE'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'smallint' => 'smallint', + 'int2' => 'smallint', + 'serial' => 'integer', + 'serial4' => 'integer', + 'int' => 'integer', + 'int4' => 'integer', + 'integer' => 'integer', + 'bigserial' => 'bigint', + 'serial8' => 'bigint', + 'bigint' => 'bigint', + 'int8' => 'bigint', + 'bool' => 'boolean', + 'boolean' => 'boolean', + 'text' => 'text', + 'varchar' => 'string', + 'interval' => 'string', + '_varchar' => 'string', + 'char' => 'string', + 'bpchar' => 'string', + 'inet' => 'string', + 'date' => 'date', + 'datetime' => 'datetime', + 'timestamp' => 'datetime', + 'timestamptz' => 'datetimetz', + 'time' => 'time', + 'timetz' => 'time', + 'float' => 'float', + 'float4' => 'float', + 'float8' => 'float', + 'double' => 'float', + 'double precision' => 'float', + 'real' => 'float', + 'decimal' => 'decimal', + 'money' => 'decimal', + 'numeric' => 'decimal', + 'year' => 'date', + 'uuid' => 'guid', + 'bytea' => 'blob', + ); + } + + /** + * {@inheritDoc} + */ + public function getVarcharMaxLength() + { + return 65535; + } + + /** + * {@inheritdoc} + */ + public function getBinaryMaxLength() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function getBinaryDefaultLength() + { + return 0; + } + + /** + * {@inheritDoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\PostgreSQLKeywords'; + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $field) + { + return 'BYTEA'; + } + + /** + * {@inheritdoc} + */ + public function quoteStringLiteral($str) + { + $str = str_replace('\\', '\\\\', $str); // PostgreSQL requires backslashes to be escaped aswell. + + return parent::quoteStringLiteral($str); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywhere11Platform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywhere11Platform.php new file mode 100644 index 00000000..44ba707b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywhere11Platform.php @@ -0,0 +1,47 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +/** + * The SQLAnywhere11Platform provides the behavior, features and SQL dialect of the + * SAP Sybase SQL Anywhere 11 database platform. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywhere11Platform extends SQLAnywherePlatform +{ + /** + * {@inheritdoc} + */ + public function getRegexpExpression() + { + return 'REGEXP'; + } + + /** + * {@inheritdoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhere11Keywords'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywhere12Platform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywhere12Platform.php new file mode 100644 index 00000000..17b413ec --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywhere12Platform.php @@ -0,0 +1,135 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Sequence; + +/** + * The SQLAnywhere12Platform provides the behavior, features and SQL dialect of the + * SAP Sybase SQL Anywhere 12 database platform. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywhere12Platform extends SQLAnywhere11Platform +{ + /** + * {@inheritdoc} + */ + public function getCreateSequenceSQL(Sequence $sequence) + { + return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize() . + ' START WITH ' . $sequence->getInitialValue() . + ' MINVALUE ' . $sequence->getInitialValue(); + } + + /** + * {@inheritdoc} + */ + public function getAlterSequenceSQL(Sequence $sequence) + { + return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize(); + } + + /** + * {@inheritdoc} + */ + public function getDateTimeTzFormatString() + { + return 'Y-m-d H:i:s.uP'; + } + + /** + * {@inheritdoc} + */ + public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIMESTAMP WITH TIME ZONE'; + } + + /** + * {@inheritdoc} + */ + public function getDropSequenceSQL($sequence) + { + if ($sequence instanceof Sequence) { + $sequence = $sequence->getQuotedName($this); + } + + return 'DROP SEQUENCE ' . $sequence; + } + + /** + * {@inheritdoc} + */ + public function getListSequencesSQL($database) + { + return 'SELECT sequence_name, increment_by, start_with, min_value FROM SYS.SYSSEQUENCE'; + } + + /** + * {@inheritdoc} + */ + public function getSequenceNextValSQL($sequenceName) + { + return 'SELECT ' . $sequenceName . '.NEXTVAL'; + } + + /** + * {@inheritdoc} + */ + public function supportsSequences() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function getAdvancedIndexOptionsSQL(Index $index) + { + if ( ! $index->isPrimary() && $index->isUnique() && $index->hasFlag('with_nulls_not_distinct')) { + return ' WITH NULLS NOT DISTINCT' . parent::getAdvancedIndexOptionsSQL($index); + } + + return parent::getAdvancedIndexOptionsSQL($index); + } + + /** + * {@inheritdoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhere12Keywords'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + parent::initializeDoctrineTypeMappings(); + $this->doctrineTypeMapping['timestamp with time zone'] = 'datetime'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywhere16Platform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywhere16Platform.php new file mode 100644 index 00000000..6808d730 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywhere16Platform.php @@ -0,0 +1,60 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\Common\Proxy\Exception\UnexpectedValueException; +use Doctrine\DBAL\Schema\Index; + +/** + * The SQLAnywhere16Platform provides the behavior, features and SQL dialect of the + * SAP Sybase SQL Anywhere 16 database platform. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywhere16Platform extends SQLAnywhere12Platform +{ + /** + * {@inheritdoc} + */ + protected function getAdvancedIndexOptionsSQL(Index $index) + { + if ($index->hasFlag('with_nulls_distinct') && $index->hasFlag('with_nulls_not_distinct')) { + throw new UnexpectedValueException( + 'An Index can either have a "with_nulls_distinct" or "with_nulls_not_distinct" flag but not both.' + ); + } + + if ( ! $index->isPrimary() && $index->isUnique() && $index->hasFlag('with_nulls_distinct')) { + return ' WITH NULLS DISTINCT' . parent::getAdvancedIndexOptionsSQL($index); + } + + return parent::getAdvancedIndexOptionsSQL($index); + } + + /** + * {@inheritdoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhere16Keywords'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywherePlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywherePlatform.php new file mode 100644 index 00000000..841bdadb --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywherePlatform.php @@ -0,0 +1,1491 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\LockMode; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ColumnDiff; +use Doctrine\DBAL\Schema\Constraint; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Identifier; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\TableDiff; + +/** + * The SQLAnywherePlatform provides the behavior, features and SQL dialect of the + * SAP Sybase SQL Anywhere 10 database platform. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywherePlatform extends AbstractPlatform +{ + /** + * @var integer + */ + const FOREIGN_KEY_MATCH_SIMPLE = 1; + /** + * @var integer + */ + const FOREIGN_KEY_MATCH_FULL = 2; + /** + * @var integer + */ + const FOREIGN_KEY_MATCH_SIMPLE_UNIQUE = 129; + /** + * @var integer + */ + const FOREIGN_KEY_MATCH_FULL_UNIQUE = 130; + + /** + * {@inheritdoc} + */ + public function appendLockHint($fromClause, $lockMode) + { + switch (true) { + case $lockMode === LockMode::NONE: + return $fromClause . ' WITH (NOLOCK)'; + + case $lockMode === LockMode::PESSIMISTIC_READ: + return $fromClause . ' WITH (UPDLOCK)'; + + case $lockMode === LockMode::PESSIMISTIC_WRITE: + return $fromClause . ' WITH (XLOCK)'; + + default: + return $fromClause; + } + } + + /** + * {@inheritdoc} + * + * SQL Anywhere supports a maximum length of 128 bytes for identifiers. + */ + public function fixSchemaElementName($schemaElementName) + { + $maxIdentifierLength = $this->getMaxIdentifierLength(); + + if (strlen($schemaElementName) > $maxIdentifierLength) { + return substr($schemaElementName, 0, $maxIdentifierLength); + } + + return $schemaElementName; + } + + /** + * {@inheritdoc} + */ + public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) + { + $query = ''; + + if ($foreignKey->hasOption('match')) { + $query = ' MATCH ' . $this->getForeignKeyMatchClauseSQL($foreignKey->getOption('match')); + } + + $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); + + if ($foreignKey->hasOption('check_on_commit') && (boolean) $foreignKey->getOption('check_on_commit')) { + $query .= ' CHECK ON COMMIT'; + } + + if ($foreignKey->hasOption('clustered') && (boolean) $foreignKey->getOption('clustered')) { + $query .= ' CLUSTERED'; + } + + if ($foreignKey->hasOption('for_olap_workload') && (boolean) $foreignKey->getOption('for_olap_workload')) { + $query .= ' FOR OLAP WORKLOAD'; + } + + return $query; + } + + /** + * {@inheritdoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = array(); + $columnSql = array(); + $commentsSQL = array(); + $tableSql = array(); + $alterClauses = array(); + + /** @var \Doctrine\DBAL\Schema\Column $column */ + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $alterClauses[] = $this->getAlterTableAddColumnClause($column); + + $comment = $this->getColumnComment($column); + + if (null !== $comment && '' !== $comment) { + $commentsSQL[] = $this->getCommentOnColumnSQL( + $diff->getName($this)->getQuotedName($this), + $column->getQuotedName($this), + $comment + ); + } + } + + /** @var \Doctrine\DBAL\Schema\Column $column */ + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $alterClauses[] = $this->getAlterTableRemoveColumnClause($column); + } + + /** @var \Doctrine\DBAL\Schema\ColumnDiff $columnDiff */ + foreach ($diff->changedColumns as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + $alterClause = $this->getAlterTableChangeColumnClause($columnDiff); + + if (null !== $alterClause) { + $alterClauses[] = $alterClause; + } + + if ($columnDiff->hasChanged('comment')) { + $column = $columnDiff->column; + + $commentsSQL[] = $this->getCommentOnColumnSQL( + $diff->getName($this)->getQuotedName($this), + $column->getQuotedName($this), + $this->getColumnComment($column) + ); + } + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' . + $this->getAlterTableRenameColumnClause($oldColumnName, $column); + } + + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + if ( ! empty($alterClauses)) { + $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' . implode(", ", $alterClauses); + } + + $sql = array_merge($sql, $commentsSQL); + + if ($diff->newName !== false) { + $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' . + $this->getAlterTableRenameTableClause($diff->getNewName()); + } + + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff) + ); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * Returns the SQL clause for creating a column in a table alteration. + * + * @param Column $column The column to add. + * + * @return string + */ + protected function getAlterTableAddColumnClause(Column $column) + { + return 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + } + + /** + * Returns the SQL clause for altering a table. + * + * @param Identifier $tableName The quoted name of the table to alter. + * + * @return string + */ + protected function getAlterTableClause(Identifier $tableName) + { + return 'ALTER TABLE ' . $tableName->getQuotedName($this); + } + + /** + * Returns the SQL clause for dropping a column in a table alteration. + * + * @param Column $column The column to drop. + * + * @return string + */ + protected function getAlterTableRemoveColumnClause(Column $column) + { + return 'DROP ' . $column->getQuotedName($this); + } + + /** + * Returns the SQL clause for renaming a column in a table alteration. + * + * @param string $oldColumnName The quoted name of the column to rename. + * @param Column $column The column to rename to. + * + * @return string + */ + protected function getAlterTableRenameColumnClause($oldColumnName, Column $column) + { + $oldColumnName = new Identifier($oldColumnName); + + return 'RENAME ' . $oldColumnName->getQuotedName($this) .' TO ' . $column->getQuotedName($this); + } + + /** + * Returns the SQL clause for renaming a table in a table alteration. + * + * @param Identifier $newTableName The quoted name of the table to rename to. + * + * @return string + */ + protected function getAlterTableRenameTableClause(Identifier $newTableName) + { + return 'RENAME ' . $newTableName->getQuotedName($this); + } + + /** + * Returns the SQL clause for altering a column in a table alteration. + * + * This method returns null in case that only the column comment has changed. + * Changes in column comments have to be handled differently. + * + * @param ColumnDiff $columnDiff The diff of the column to alter. + * + * @return string|null + */ + protected function getAlterTableChangeColumnClause(ColumnDiff $columnDiff) + { + $column = $columnDiff->column; + + // Do not return alter clause if only comment has changed. + if ( ! ($columnDiff->hasChanged('comment') && count($columnDiff->changedProperties) === 1)) { + $columnAlterationClause = 'ALTER ' . + $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + + if ($columnDiff->hasChanged('default') && null === $column->getDefault()) { + $columnAlterationClause .= ', ALTER ' . $column->getQuotedName($this) . ' DROP DEFAULT'; + } + + return $columnAlterationClause; + } + } + + /** + * {@inheritdoc} + */ + public function getBigIntTypeDeclarationSQL(array $columnDef) + { + $columnDef['integer_type'] = 'BIGINT'; + + return $this->_getCommonIntegerTypeDeclarationSQL($columnDef); + } + + /** + * {@inheritdoc} + */ + public function getBinaryDefaultLength() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function getBinaryMaxLength() + { + return 32767; + } + + /** + * {@inheritdoc} + */ + public function getBlobTypeDeclarationSQL(array $field) + { + return 'LONG BINARY'; + } + + /** + * {@inheritdoc} + * + * BIT type columns require an explicit NULL declaration + * in SQL Anywhere if they shall be nullable. + * Otherwise by just omitting the NOT NULL clause, + * SQL Anywhere will declare them NOT NULL nonetheless. + */ + public function getBooleanTypeDeclarationSQL(array $columnDef) + { + $nullClause = isset($columnDef['notnull']) && (boolean) $columnDef['notnull'] === false ? ' NULL' : ''; + + return 'BIT' . $nullClause; + } + + /** + * {@inheritdoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + return 'TEXT'; + } + + /** + * {@inheritdoc} + */ + public function getCommentOnColumnSQL($tableName, $columnName, $comment) + { + $tableName = new Identifier($tableName); + $columnName = new Identifier($columnName); + $comment = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment); + + return "COMMENT ON COLUMN " . $tableName->getQuotedName($this) . '.' . $columnName->getQuotedName($this) . + " IS $comment"; + } + + /** + * {@inheritdoc} + */ + public function getConcatExpression() + { + return 'STRING(' . implode(', ', (array) func_get_args()) . ')'; + } + + /** + * {@inheritdoc} + */ + public function getCreateConstraintSQL(Constraint $constraint, $table) + { + if ($constraint instanceof ForeignKeyConstraint) { + return $this->getCreateForeignKeySQL($constraint, $table); + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + return 'ALTER TABLE ' . $table . + ' ADD ' . $this->getTableConstraintDeclarationSQL($constraint, $constraint->getQuotedName($this)); + } + + /** + * {@inheritdoc} + */ + public function getCreateDatabaseSQL($database) + { + $database = new Identifier($database); + + return "CREATE DATABASE '" . $database->getName() . "'"; + } + + /** + * {@inheritdoc} + * + * Appends SQL Anywhere specific flags if given. + */ + public function getCreateIndexSQL(Index $index, $table) + { + return parent::getCreateIndexSQL($index, $table). $this->getAdvancedIndexOptionsSQL($index); + } + + /** + * {@inheritdoc} + */ + public function getCreatePrimaryKeySQL(Index $index, $table) + { + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + return 'ALTER TABLE ' . $table . ' ADD ' . $this->getPrimaryKeyDeclarationSQL($index); + } + + /** + * {@inheritdoc} + */ + public function getCreateTemporaryTableSnippetSQL() + { + return 'CREATE ' . $this->getTemporaryTableSQL() . ' TABLE'; + } + + /** + * {@inheritdoc} + */ + public function getCreateViewSQL($name, $sql) + { + return 'CREATE VIEW ' . $name . ' AS ' . $sql; + } + + /** + * {@inheritdoc} + */ + public function getCurrentDateSQL() + { + return 'CURRENT DATE'; + } + + /** + * {@inheritdoc} + */ + public function getCurrentTimeSQL() + { + return 'CURRENT TIME'; + } + + /** + * {@inheritdoc} + */ + public function getCurrentTimestampSQL() + { + return 'CURRENT TIMESTAMP'; + } + + /** + * {@inheritdoc} + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + $factorClause = ''; + + if ('-' === $operator) { + $factorClause = '-1 * '; + } + + return 'DATEADD(' . $unit . ', ' . $factorClause . $interval . ', ' . $date . ')'; + } + + /** + * {@inheritdoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return 'DATEDIFF(day, ' . $date2 . ', ' . $date1 . ')'; + } + + /** + * {@inheritdoc} + */ + public function getDateTimeFormatString() + { + return 'Y-m-d H:i:s.u'; + } + + /** + * {@inheritdoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATETIME'; + } + + /** + * {@inheritdoc} + */ + public function getDateTimeTzFormatString() + { + return $this->getDateTimeFormatString(); + } + + /** + * {@inheritdoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritdoc} + */ + public function getDefaultTransactionIsolationLevel() + { + return Connection::TRANSACTION_READ_UNCOMMITTED; + } + + /** + * {@inheritdoc} + */ + public function getDropDatabaseSQL($database) + { + $database = new Identifier($database); + + return "DROP DATABASE '" . $database->getName() . "'"; + } + + /** + * {@inheritdoc} + */ + public function getDropIndexSQL($index, $table = null) + { + if ($index instanceof Index) { + $index = $index->getQuotedName($this); + } + + if ( ! is_string($index)) { + throw new \InvalidArgumentException( + 'SQLAnywherePlatform::getDropIndexSQL() expects $index parameter to be string or ' . + '\Doctrine\DBAL\Schema\Index.' + ); + } + + if ( ! isset($table)) { + return 'DROP INDEX ' . $index; + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + if ( ! is_string($table)) { + throw new \InvalidArgumentException( + 'SQLAnywherePlatform::getDropIndexSQL() expects $table parameter to be string or ' . + '\Doctrine\DBAL\Schema\Table.' + ); + } + + return 'DROP INDEX ' . $table . '.' . $index; + } + + /** + * {@inheritdoc} + */ + public function getDropViewSQL($name) + { + return 'DROP VIEW ' . $name; + } + + /** + * {@inheritdoc} + */ + public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey) + { + $sql = ''; + $foreignKeyName = $foreignKey->getName(); + $localColumns = $foreignKey->getQuotedLocalColumns($this); + $foreignColumns = $foreignKey->getQuotedForeignColumns($this); + $foreignTableName = $foreignKey->getQuotedForeignTableName($this); + + if ( ! empty($foreignKeyName)) { + $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' '; + } + + if (empty($localColumns)) { + throw new \InvalidArgumentException("Incomplete definition. 'local' required."); + } + + if (empty($foreignColumns)) { + throw new \InvalidArgumentException("Incomplete definition. 'foreign' required."); + } + + if (empty($foreignTableName)) { + throw new \InvalidArgumentException("Incomplete definition. 'foreignTable' required."); + } + + if ($foreignKey->hasOption('notnull') && (boolean) $foreignKey->getOption('notnull')) { + $sql .= 'NOT NULL '; + } + + return $sql . + 'FOREIGN KEY (' . $this->getIndexFieldDeclarationListSQL($localColumns) . ') ' . + 'REFERENCES ' . $foreignKey->getQuotedForeignTableName($this) . + ' (' . $this->getIndexFieldDeclarationListSQL($foreignColumns) . ')'; + } + + /** + * Returns foreign key MATCH clause for given type. + * + * @param integer $type The foreign key match type + * + * @return string + * + * @throws \InvalidArgumentException if unknown match type given + */ + public function getForeignKeyMatchClauseSQL($type) + { + switch ((int) $type) { + case self::FOREIGN_KEY_MATCH_SIMPLE: + return 'SIMPLE'; + break; + case self::FOREIGN_KEY_MATCH_FULL: + return 'FULL'; + break; + case self::FOREIGN_KEY_MATCH_SIMPLE_UNIQUE: + return 'UNIQUE SIMPLE'; + break; + case self::FOREIGN_KEY_MATCH_FULL_UNIQUE: + return 'UNIQUE FULL'; + default: + throw new \InvalidArgumentException('Invalid foreign key match type: ' . $type); + } + } + + /** + * {@inheritdoc} + */ + public function getForeignKeyReferentialActionSQL($action) + { + // NO ACTION is not supported, therefore falling back to RESTRICT. + if (strtoupper($action) === 'NO ACTION') { + return 'RESTRICT'; + } + + return parent::getForeignKeyReferentialActionSQL($action); + } + + /** + * {@inheritdoc} + */ + public function getForUpdateSQL() + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function getGuidExpression() + { + return 'NEWID()'; + } + + /** + * {@inheritdoc} + */ + public function getGuidTypeDeclarationSQL(array $field) + { + return 'UNIQUEIDENTIFIER'; + } + + /** + * {@inheritdoc} + */ + public function getIndexDeclarationSQL($name, Index $index) + { + // Index declaration in statements like CREATE TABLE is not supported. + throw DBALException::notSupported(__METHOD__); + } + + /** + * {@inheritdoc} + */ + public function getIntegerTypeDeclarationSQL(array $columnDef) + { + $columnDef['integer_type'] = 'INT'; + + return $this->_getCommonIntegerTypeDeclarationSQL($columnDef); + } + + /** + * {@inheritdoc} + */ + public function getListDatabasesSQL() + { + return 'SELECT db_name(number) AS name FROM sa_db_list()'; + } + + /** + * {@inheritdoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + $user = 'USER_NAME()'; + + if (strpos($table, '.') !== false) { + list($user, $table) = explode('.', $table); + $user = $this->quoteStringLiteral($user); + } + + return "SELECT col.column_name, + COALESCE(def.user_type_name, def.domain_name) AS 'type', + def.declared_width AS 'length', + def.scale, + CHARINDEX('unsigned', def.domain_name) AS 'unsigned', + IF col.nulls = 'Y' THEN 0 ELSE 1 ENDIF AS 'notnull', + col.\"default\", + def.is_autoincrement AS 'autoincrement', + rem.remarks AS 'comment' + FROM sa_describe_query('SELECT * FROM \"$table\"') AS def + JOIN SYS.SYSTABCOL AS col + ON col.table_id = def.base_table_id AND col.column_id = def.base_column_id + LEFT JOIN SYS.SYSREMARK AS rem + ON col.object_id = rem.object_id + WHERE def.base_owner_name = $user + ORDER BY def.base_column_id ASC"; + } + + /** + * {@inheritdoc} + * + * @todo Where is this used? Which information should be retrieved? + */ + public function getListTableConstraintsSQL($table) + { + $user = ''; + + if (strpos($table, '.') !== false) { + list($user, $table) = explode('.', $table); + $user = $this->quoteStringLiteral($user); + $table = $this->quoteStringLiteral($table); + } else { + $table = $this->quoteStringLiteral($table); + } + + return "SELECT con.* + FROM SYS.SYSCONSTRAINT AS con + JOIN SYS.SYSTAB AS tab ON con.table_object_id = tab.object_id + WHERE tab.table_name = $table + AND tab.creator = USER_ID($user)"; + } + + /** + * {@inheritdoc} + */ + public function getListTableForeignKeysSQL($table) + { + $user = ''; + + if (strpos($table, '.') !== false) { + list($user, $table) = explode('.', $table); + $user = $this->quoteStringLiteral($user); + $table = $this->quoteStringLiteral($table); + } else { + $table = $this->quoteStringLiteral($table); + } + + return "SELECT fcol.column_name AS local_column, + ptbl.table_name AS foreign_table, + pcol.column_name AS foreign_column, + idx.index_name, + IF fk.nulls = 'N' + THEN 1 + ELSE NULL + ENDIF AS notnull, + CASE ut.referential_action + WHEN 'C' THEN 'CASCADE' + WHEN 'D' THEN 'SET DEFAULT' + WHEN 'N' THEN 'SET NULL' + WHEN 'R' THEN 'RESTRICT' + ELSE NULL + END AS on_update, + CASE dt.referential_action + WHEN 'C' THEN 'CASCADE' + WHEN 'D' THEN 'SET DEFAULT' + WHEN 'N' THEN 'SET NULL' + WHEN 'R' THEN 'RESTRICT' + ELSE NULL + END AS on_delete, + IF fk.check_on_commit = 'Y' + THEN 1 + ELSE NULL + ENDIF AS check_on_commit, -- check_on_commit flag + IF ftbl.clustered_index_id = idx.index_id + THEN 1 + ELSE NULL + ENDIF AS 'clustered', -- clustered flag + IF fk.match_type = 0 + THEN NULL + ELSE fk.match_type + ENDIF AS 'match', -- match option + IF pidx.max_key_distance = 1 + THEN 1 + ELSE NULL + ENDIF AS for_olap_workload -- for_olap_workload flag + FROM SYS.SYSFKEY AS fk + JOIN SYS.SYSIDX AS idx + ON fk.foreign_table_id = idx.table_id + AND fk.foreign_index_id = idx.index_id + JOIN SYS.SYSPHYSIDX pidx + ON idx.table_id = pidx.table_id + AND idx.phys_index_id = pidx.phys_index_id + JOIN SYS.SYSTAB AS ptbl + ON fk.primary_table_id = ptbl.table_id + JOIN SYS.SYSTAB AS ftbl + ON fk.foreign_table_id = ftbl.table_id + JOIN SYS.SYSIDXCOL AS idxcol + ON idx.table_id = idxcol.table_id + AND idx.index_id = idxcol.index_id + JOIN SYS.SYSTABCOL AS pcol + ON ptbl.table_id = pcol.table_id + AND idxcol.primary_column_id = pcol.column_id + JOIN SYS.SYSTABCOL AS fcol + ON ftbl.table_id = fcol.table_id + AND idxcol.column_id = fcol.column_id + LEFT JOIN SYS.SYSTRIGGER ut + ON fk.foreign_table_id = ut.foreign_table_id + AND fk.foreign_index_id = ut.foreign_key_id + AND ut.event = 'C' + LEFT JOIN SYS.SYSTRIGGER dt + ON fk.foreign_table_id = dt.foreign_table_id + AND fk.foreign_index_id = dt.foreign_key_id + AND dt.event = 'D' + WHERE ftbl.table_name = $table + AND ftbl.creator = USER_ID($user) + ORDER BY fk.foreign_index_id ASC, idxcol.sequence ASC"; + } + + /** + * {@inheritdoc} + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + $user = ''; + + if (strpos($table, '.') !== false) { + list($user, $table) = explode('.', $table); + $user = $this->quoteStringLiteral($user); + $table = $this->quoteStringLiteral($table); + } else { + $table = $this->quoteStringLiteral($table); + } + + return "SELECT idx.index_name AS key_name, + IF idx.index_category = 1 + THEN 1 + ELSE 0 + ENDIF AS 'primary', + col.column_name, + IF idx.\"unique\" IN(1, 2, 5) + THEN 0 + ELSE 1 + ENDIF AS non_unique, + IF tbl.clustered_index_id = idx.index_id + THEN 1 + ELSE NULL + ENDIF AS 'clustered', -- clustered flag + IF idx.\"unique\" = 5 + THEN 1 + ELSE NULL + ENDIF AS with_nulls_not_distinct, -- with_nulls_not_distinct flag + IF pidx.max_key_distance = 1 + THEN 1 + ELSE NULL + ENDIF AS for_olap_workload -- for_olap_workload flag + FROM SYS.SYSIDX AS idx + JOIN SYS.SYSPHYSIDX pidx + ON idx.table_id = pidx.table_id + AND idx.phys_index_id = pidx.phys_index_id + JOIN SYS.SYSIDXCOL AS idxcol + ON idx.table_id = idxcol.table_id AND idx.index_id = idxcol.index_id + JOIN SYS.SYSTABCOL AS col + ON idxcol.table_id = col.table_id AND idxcol.column_id = col.column_id + JOIN SYS.SYSTAB AS tbl + ON idx.table_id = tbl.table_id + WHERE tbl.table_name = $table + AND tbl.creator = USER_ID($user) + AND idx.index_category != 2 -- exclude indexes implicitly created by foreign key constraints + ORDER BY idx.index_id ASC, idxcol.sequence ASC"; + } + + /** + * {@inheritdoc} + */ + public function getListTablesSQL() + { + return "SELECT tbl.table_name + FROM SYS.SYSTAB AS tbl + JOIN SYS.SYSUSER AS usr ON tbl.creator = usr.user_id + JOIN dbo.SYSOBJECTS AS obj ON tbl.object_id = obj.id + WHERE tbl.table_type IN(1, 3) -- 'BASE', 'GBL TEMP' + AND usr.user_name NOT IN('SYS', 'dbo', 'rs_systabgroup') -- exclude system users + AND obj.type = 'U' -- user created tables only + ORDER BY tbl.table_name ASC"; + } + + /** + * {@inheritdoc} + * + * @todo Where is this used? Which information should be retrieved? + */ + public function getListUsersSQL() + { + return 'SELECT * FROM SYS.SYSUSER ORDER BY user_name ASC'; + } + + /** + * {@inheritdoc} + */ + public function getListViewsSQL($database) + { + return "SELECT tbl.table_name, v.view_def + FROM SYS.SYSVIEW v + JOIN SYS.SYSTAB tbl ON v.view_object_id = tbl.object_id + JOIN SYS.SYSUSER usr ON tbl.creator = usr.user_id + JOIN dbo.SYSOBJECTS obj ON tbl.object_id = obj.id + WHERE usr.user_name NOT IN('SYS', 'dbo', 'rs_systabgroup') -- exclude system users + ORDER BY tbl.table_name ASC"; + } + + /** + * {@inheritdoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'LOCATE(' . $str . ', ' . $substr . ')'; + } + + return 'LOCATE(' . $str . ', ' . $substr . ', ' . $startPos . ')'; + } + + /** + * {@inheritdoc} + */ + public function getMaxIdentifierLength() + { + return 128; + } + + /** + * {@inheritdoc} + */ + public function getMd5Expression($column) + { + return "HASH(" . $column . ", 'MD5')"; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'sqlanywhere'; + } + + /** + * Obtain DBMS specific SQL code portion needed to set a primary key + * declaration to be used in statements like ALTER TABLE. + * + * @param Index $index Index definition + * @param string $name Name of the primary key + * + * @return string DBMS specific SQL code portion needed to set a primary key + * + * @throws \InvalidArgumentException if the given index is not a primary key. + */ + public function getPrimaryKeyDeclarationSQL(Index $index, $name = null) + { + if ( ! $index->isPrimary()) { + throw new \InvalidArgumentException( + 'Can only create primary key declarations with getPrimaryKeyDeclarationSQL()' + ); + } + + return $this->getTableConstraintDeclarationSQL($index, $name); + } + + /** + * {@inheritdoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'SET TEMPORARY OPTION isolation_level = ' . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritdoc} + */ + public function getSmallIntTypeDeclarationSQL(array $columnDef) + { + $columnDef['integer_type'] = 'SMALLINT'; + + return $this->_getCommonIntegerTypeDeclarationSQL($columnDef); + } + + /** + * Returns the SQL statement for starting an existing database. + * + * In SQL Anywhere you can start and stop databases on a + * database server instance. + * This is a required statement after having created a new database + * as it has to be explicitly started to be usable. + * SQL Anywhere does not automatically start a database after creation! + * + * @param string $database Name of the database to start. + * + * @return string + */ + public function getStartDatabaseSQL($database) + { + $database = new Identifier($database); + + return "START DATABASE '" . $database->getName() . "' AUTOSTOP OFF"; + } + + /** + * Returns the SQL statement for stopping a running database. + * + * In SQL Anywhere you can start and stop databases on a + * database server instance. + * This is a required statement before dropping an existing database + * as it has to be explicitly stopped before it can be dropped. + * + * @param string $database Name of the database to stop. + * + * @return string + */ + public function getStopDatabaseSQL($database) + { + $database = new Identifier($database); + + return 'STOP DATABASE "' . $database->getName() . '" UNCONDITIONALLY'; + } + + /** + * {@inheritdoc} + */ + public function getSubstringExpression($value, $from, $length = null) + { + if (null === $length) { + return 'SUBSTRING(' . $value . ', ' . $from . ')'; + } + + return 'SUBSTRING(' . $value . ', ' . $from . ', ' . $length . ')'; + } + + /** + * {@inheritdoc} + */ + public function getTemporaryTableSQL() + { + return 'GLOBAL TEMPORARY'; + } + + /** + * {@inheritdoc} + */ + public function getTimeFormatString() + { + return 'H:i:s.u'; + } + + /** + * {@inheritdoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIME'; + } + + /** + * {@inheritdoc} + */ + public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false) + { + if ( ! $char) { + switch ($pos) { + case self::TRIM_LEADING: + return $this->getLtrimExpression($str); + case self::TRIM_TRAILING: + return $this->getRtrimExpression($str); + default: + return 'TRIM(' . $str . ')'; + } + } + + $pattern = "'%[^' + $char + ']%'"; + + switch ($pos) { + case self::TRIM_LEADING: + return 'SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))'; + case self::TRIM_TRAILING: + return 'REVERSE(SUBSTR(REVERSE(' . $str . '), PATINDEX(' . $pattern . ', REVERSE(' . $str . '))))'; + default: + return + 'REVERSE(SUBSTR(REVERSE(SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))), ' . + 'PATINDEX(' . $pattern . ', REVERSE(SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))))))'; + } + } + + /** + * {@inheritdoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + $tableIdentifier = new Identifier($tableName); + + return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this); + } + + /** + * {@inheritdoc} + */ + public function getUniqueConstraintDeclarationSQL($name, Index $index) + { + if ($index->isPrimary()) { + throw new \InvalidArgumentException( + 'Cannot create primary key constraint declarations with getUniqueConstraintDeclarationSQL().' + ); + } + + if ( ! $index->isUnique()) { + throw new \InvalidArgumentException( + 'Can only create unique constraint declarations, no common index declarations with ' . + 'getUniqueConstraintDeclarationSQL().' + ); + } + + return $this->getTableConstraintDeclarationSQL($index, $name); + } + + /** + * {@inheritdoc} + */ + public function getVarcharDefaultLength() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function getVarcharMaxLength() + { + return 32767; + } + + /** + * {@inheritdoc} + */ + public function hasNativeGuidType() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function prefersIdentityColumns() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function supportsCommentOnStatement() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + $unsigned = ! empty($columnDef['unsigned']) ? 'UNSIGNED ' : ''; + $autoincrement = ! empty($columnDef['autoincrement']) ? ' IDENTITY' : ''; + + return $unsigned . $columnDef['integer_type'] . $autoincrement; + } + + /** + * {@inheritdoc} + */ + protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) + { + $columnListSql = $this->getColumnDeclarationListSQL($columns); + $indexSql = array(); + + if ( ! empty($options['uniqueConstraints'])) { + foreach ((array) $options['uniqueConstraints'] as $name => $definition) { + $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition); + } + } + + if ( ! empty($options['indexes'])) { + /** @var \Doctrine\DBAL\Schema\Index $index */ + foreach ((array) $options['indexes'] as $index) { + $indexSql[] = $this->getCreateIndexSQL($index, $tableName); + } + } + + if ( ! empty($options['primary'])) { + $flags = ''; + + if (isset($options['primary_index']) && $options['primary_index']->hasFlag('clustered')) { + $flags = ' CLUSTERED '; + } + + $columnListSql .= ', PRIMARY KEY' . $flags . ' (' . implode(', ', array_unique(array_values((array) $options['primary']))) . ')'; + } + + if ( ! empty($options['foreignKeys'])) { + foreach ((array) $options['foreignKeys'] as $definition) { + $columnListSql .= ', ' . $this->getForeignKeyDeclarationSQL($definition); + } + } + + $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql; + $check = $this->getCheckDeclarationSQL($columns); + + if ( ! empty($check)) { + $query .= ', ' . $check; + } + + $query .= ')'; + + return array_merge(array($query), $indexSql); + } + + /** + * {@inheritdoc} + */ + protected function _getTransactionIsolationLevelSQL($level) + { + switch ($level) { + case Connection::TRANSACTION_READ_UNCOMMITTED: + return 0; + case Connection::TRANSACTION_READ_COMMITTED: + return 1; + case Connection::TRANSACTION_REPEATABLE_READ: + return 2; + case Connection::TRANSACTION_SERIALIZABLE: + return 3; + default: + throw new \InvalidArgumentException('Invalid isolation level:' . $level); + } + } + + /** + * {@inheritdoc} + */ + protected function doModifyLimitQuery($query, $limit, $offset) + { + $limitOffsetClause = ''; + + if ($limit > 0) { + $limitOffsetClause = 'TOP ' . $limit . ' '; + } + + if ($offset > 0) { + if ($limit == 0) { + $limitOffsetClause = 'TOP ALL '; + } + + $limitOffsetClause .= 'START AT ' . ($offset + 1) . ' '; + } + + if ($limitOffsetClause) { + return preg_replace('/^\s*(SELECT\s+(DISTINCT\s+)?)/i', '\1' . $limitOffsetClause, $query); + } + + return $query; + } + + /** + * Return the INDEX query section dealing with non-standard + * SQL Anywhere options. + * + * @param Index $index Index definition + * + * @return string + */ + protected function getAdvancedIndexOptionsSQL(Index $index) + { + $sql = ''; + + if ( ! $index->isPrimary() && $index->hasFlag('for_olap_workload')) { + $sql .= ' FOR OLAP WORKLOAD'; + } + + return $sql; + } + + /** + * {@inheritdoc} + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed + ? 'BINARY(' . ($length ?: $this->getBinaryDefaultLength()) . ')' + : 'VARBINARY(' . ($length ?: $this->getBinaryDefaultLength()) . ')'; + } + + /** + * Returns the SQL snippet for creating a table constraint. + * + * @param Constraint $constraint The table constraint to create the SQL snippet for. + * @param string|null $name The table constraint name to use if any. + * + * @return string + * + * @throws \InvalidArgumentException if the given table constraint type is not supported by this method. + */ + protected function getTableConstraintDeclarationSQL(Constraint $constraint, $name = null) + { + if ($constraint instanceof ForeignKeyConstraint) { + return $this->getForeignKeyDeclarationSQL($constraint); + } + + if ( ! $constraint instanceof Index) { + throw new \InvalidArgumentException('Unsupported constraint type: ' . get_class($constraint)); + } + + if ( ! $constraint->isPrimary() && ! $constraint->isUnique()) { + throw new \InvalidArgumentException( + 'Can only create primary, unique or foreign key constraint declarations, no common index declarations ' . + 'with getTableConstraintDeclarationSQL().' + ); + } + + $constraintColumns = $constraint->getQuotedColumns($this); + + if (empty($constraintColumns)) { + throw new \InvalidArgumentException("Incomplete definition. 'columns' required."); + } + + $sql = ''; + $flags = ''; + + if ( ! empty($name)) { + $name = new Identifier($name); + $sql .= 'CONSTRAINT ' . $name->getQuotedName($this) . ' '; + } + + if ($constraint->hasFlag('clustered')) { + $flags = 'CLUSTERED '; + } + + if ($constraint->isPrimary()) { + return $sql . 'PRIMARY KEY ' . $flags . '('. $this->getIndexFieldDeclarationListSQL($constraintColumns) . ')'; + } + + return $sql . 'UNIQUE ' . $flags . '('. $this->getIndexFieldDeclarationListSQL($constraintColumns) . ')'; + } + + /** + * {@inheritdoc} + */ + protected function getCreateIndexSQLFlags(Index $index) + { + $type = ''; + if ($index->hasFlag('virtual')) { + $type .= 'VIRTUAL '; + } + + if ($index->isUnique()) { + $type .= 'UNIQUE '; + } + + if ($index->hasFlag('clustered')) { + $type .= 'CLUSTERED '; + } + + return $type; + } + + /** + * {@inheritdoc} + */ + protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) + { + return array( + 'ALTER INDEX ' . $oldIndexName . ' ON ' . $tableName . ' RENAME TO ' . $index->getQuotedName($this) + ); + } + + /** + * {@inheritdoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhereKeywords'; + } + + /** + * {@inheritdoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed + ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(' . $this->getVarcharDefaultLength() . ')') + : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(' . $this->getVarcharDefaultLength() . ')'); + } + + /** + * {@inheritdoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'char' => 'string', + 'long nvarchar' => 'text', + 'long varchar' => 'text', + 'nchar' => 'string', + 'ntext' => 'text', + 'nvarchar' => 'string', + 'text' => 'text', + 'uniqueidentifierstr' => 'guid', + 'varchar' => 'string', + 'xml' => 'text', + 'bigint' => 'bigint', + 'unsigned bigint' => 'bigint', + 'bit' => 'boolean', + 'decimal' => 'decimal', + 'double' => 'float', + 'float' => 'float', + 'int' => 'integer', + 'integer' => 'integer', + 'unsigned int' => 'integer', + 'numeric' => 'decimal', + 'smallint' => 'smallint', + 'unsigned smallint', 'smallint', + 'tinyint' => 'smallint', + 'unsigned tinyint', 'smallint', + 'money' => 'decimal', + 'smallmoney' => 'decimal', + 'long varbit' => 'text', + 'varbit' => 'string', + 'date' => 'date', + 'datetime' => 'datetime', + 'smalldatetime' => 'datetime', + 'time' => 'time', + 'timestamp' => 'datetime', + 'binary' => 'binary', + 'image' => 'blob', + 'long binary' => 'blob', + 'uniqueidentifier' => 'guid', + 'varbinary' => 'binary', + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAzurePlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAzurePlatform.php new file mode 100644 index 00000000..71829cd0 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAzurePlatform.php @@ -0,0 +1,50 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\Schema\Table; + +/** + * Platform to ensure compatibility of Doctrine with SQL Azure + * + * On top of SQL Server 2008 the following functionality is added: + * + * - Create tables with the FEDERATED ON syntax. + */ +class SQLAzurePlatform extends SQLServer2008Platform +{ + /** + * {@inheritDoc} + */ + public function getCreateTableSQL(Table $table, $createFlags=self::CREATE_INDEXES) + { + $sql = parent::getCreateTableSQL($table, $createFlags); + + if ($table->hasOption('azure.federatedOnColumnName')) { + $distributionName = $table->getOption('azure.federatedOnDistributionName'); + $columnName = $table->getOption('azure.federatedOnColumnName'); + $stmt = ' FEDERATED ON (' . $distributionName . ' = ' . $columnName . ')'; + + $sql[0] = $sql[0] . $stmt; + } + + return $sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2005Platform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2005Platform.php new file mode 100644 index 00000000..6dc1188f --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2005Platform.php @@ -0,0 +1,63 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +/** + * Platform to ensure compatibility of Doctrine with Microsoft SQL Server 2005 version and + * higher. + * + * Differences to SQL Server 2008 are: + * + * - DATETIME2 datatype does not exist, only DATETIME which has a precision of + * 3. This is not supported by PHP DateTime, so we are emulating it by + * setting .000 manually. + * - Starting with SQLServer2005 VARCHAR(MAX), VARBINARY(MAX) and + * NVARCHAR(max) replace the old TEXT, NTEXT and IMAGE types. See + * {@link http://www.sql-server-helper.com/faq/sql-server-2005-varchar-max-p01.aspx} + * for more information. + */ +class SQLServer2005Platform extends SQLServerPlatform +{ + /** + * {@inheritDoc} + */ + public function supportsLimitOffset() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + return 'VARCHAR(MAX)'; + } + + /** + * {@inheritdoc} + * + * Returns Microsoft SQL Server 2005 specific keywords class + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLServer2005Keywords'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2008Platform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2008Platform.php new file mode 100644 index 00000000..8b1184d1 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2008Platform.php @@ -0,0 +1,119 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +/** + * Platform to ensure compatibility of Doctrine with Microsoft SQL Server 2008 version. + * + * Differences to SQL Server 2005 and before are that a new DATETIME2 type was + * introduced that has a higher precision. + */ +class SQLServer2008Platform extends SQLServer2005Platform +{ + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + // 3 - microseconds precision length + // http://msdn.microsoft.com/en-us/library/ms187819.aspx + return 'DATETIME2(6)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIME(0)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATETIMEOFFSET(6)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeFormatString() + { + return 'Y-m-d H:i:s.u'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzFormatString() + { + return 'Y-m-d H:i:s.u P'; + } + + /** + * {@inheritDoc} + */ + public function getDateFormatString() + { + return 'Y-m-d'; + } + + /** + * {@inheritDoc} + */ + public function getTimeFormatString() + { + return 'H:i:s'; + } + + /** + * {@inheritDoc} + * + * Adding Datetime2 Type + */ + protected function initializeDoctrineTypeMappings() + { + parent::initializeDoctrineTypeMappings(); + $this->doctrineTypeMapping['datetime2'] = 'datetime'; + $this->doctrineTypeMapping['date'] = 'date'; + $this->doctrineTypeMapping['time'] = 'time'; + $this->doctrineTypeMapping['datetimeoffset'] = 'datetimetz'; + } + + /** + * {@inheritdoc} + * + * Returns Microsoft SQL Server 2008 specific keywords class + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLServer2008Keywords'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2012Platform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2012Platform.php new file mode 100644 index 00000000..c9bceaff --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2012Platform.php @@ -0,0 +1,158 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\Schema\Sequence; + +/** + * Platform to ensure compatibility of Doctrine with Microsoft SQL Server 2012 version. + * + * Differences to SQL Server 2008 and before are that sequences are introduced. + * + * @author Steve Müller + */ +class SQLServer2012Platform extends SQLServer2008Platform +{ + /** + * {@inheritdoc} + */ + public function getAlterSequenceSQL(Sequence $sequence) + { + return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize(); + } + + /** + * {@inheritdoc} + */ + public function getCreateSequenceSQL(Sequence $sequence) + { + return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . + ' START WITH ' . $sequence->getInitialValue() . + ' INCREMENT BY ' . $sequence->getAllocationSize() . + ' MINVALUE ' . $sequence->getInitialValue(); + } + + /** + * {@inheritdoc} + */ + public function getDropSequenceSQL($sequence) + { + if ($sequence instanceof Sequence) { + $sequence = $sequence->getQuotedName($this); + } + + return 'DROP SEQUENCE ' . $sequence; + } + + /** + * {@inheritdoc} + */ + public function getListSequencesSQL($database) + { + return 'SELECT seq.name, + CAST( + seq.increment AS VARCHAR(MAX) + ) AS increment, -- CAST avoids driver error for sql_variant type + CAST( + seq.start_value AS VARCHAR(MAX) + ) AS start_value -- CAST avoids driver error for sql_variant type + FROM sys.sequences AS seq'; + } + + /** + * {@inheritdoc} + */ + public function getSequenceNextValSQL($sequenceName) + { + return 'SELECT NEXT VALUE FOR ' . $sequenceName; + } + + /** + * {@inheritdoc} + */ + public function supportsSequences() + { + return true; + } + + /** + * {@inheritdoc} + * + * Returns Microsoft SQL Server 2012 specific keywords class + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLServer2012Keywords'; + } + + /** + * {@inheritdoc} + */ + protected function doModifyLimitQuery($query, $limit, $offset = null) + { + if ($limit === null && $offset === null) { + return $query; + } + + // Queries using OFFSET... FETCH MUST have an ORDER BY clause + // Find the position of the last instance of ORDER BY and ensure it is not within a parenthetical statement + // but can be in a newline + $matches = array(); + $matchesCount = preg_match_all("/[\\s]+order by /i", $query, $matches, PREG_OFFSET_CAPTURE); + $orderByPos = false; + + if ($matchesCount > 0) { + $orderByPos = $matches[0][($matchesCount - 1)][1]; + } + + if ($orderByPos === false + || substr_count($query, "(", $orderByPos) - substr_count($query, ")", $orderByPos) + ) { + if (stripos($query, 'SELECT DISTINCT') === 0) { + // SQL Server won't let us order by a non-selected column in a DISTINCT query, + // so we have to do this madness. This says, order by the first column in the + // result. SQL Server's docs say that a nonordered query's result order is non- + // deterministic anyway, so this won't do anything that a bunch of update and + // deletes to the table wouldn't do anyway. + $query .= " ORDER BY 1"; + } else { + // In another DBMS, we could do ORDER BY 0, but SQL Server gets angry if you + // use constant expressions in the order by list. + $query .= " ORDER BY (SELECT 0)"; + } + } + + if ($offset === null) { + $offset = 0; + } + + // This looks somewhat like MYSQL, but limit/offset are in inverse positions + // Supposedly SQL:2008 core standard. + // Per TSQL spec, FETCH NEXT n ROWS ONLY is not valid without OFFSET n ROWS. + $query .= " OFFSET " . (int) $offset . " ROWS"; + + if ($limit !== null) { + $query .= " FETCH NEXT " . (int) $limit . " ROWS ONLY"; + } + + return $query; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php new file mode 100644 index 00000000..b0e53f44 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php @@ -0,0 +1,1627 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\LockMode; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ColumnDiff; +use Doctrine\DBAL\Schema\Identifier; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Table; + +/** + * The SQLServerPlatform provides the behavior, features and SQL dialect of the + * Microsoft SQL Server database platform. + * + * @since 2.0 + * @author Roman Borschel + * @author Jonathan H. Wage + * @author Benjamin Eberlei + * @author Steve Müller + */ +class SQLServerPlatform extends AbstractPlatform +{ + /** + * {@inheritdoc} + */ + public function getCurrentDateSQL() + { + return $this->getConvertExpression('date', 'GETDATE()'); + } + + /** + * {@inheritdoc} + */ + public function getCurrentTimeSQL() + { + return $this->getConvertExpression('time', 'GETDATE()'); + } + + /** + * Returns an expression that converts an expression of one data type to another. + * + * @param string $dataType The target native data type. Alias data types cannot be used. + * @param string $expression The SQL expression to convert. + * + * @return string + */ + private function getConvertExpression($dataType, $expression) + { + return sprintf('CONVERT(%s, %s)', $dataType, $expression); + } + + /** + * {@inheritdoc} + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + $factorClause = ''; + + if ('-' === $operator) { + $factorClause = '-1 * '; + } + + return 'DATEADD(' . $unit . ', ' . $factorClause . $interval . ', ' . $date . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return 'DATEDIFF(day, ' . $date2 . ',' . $date1 . ')'; + } + + /** + * {@inheritDoc} + * + * Microsoft SQL Server prefers "autoincrement" identity columns + * since sequences can only be emulated with a table. + */ + public function prefersIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + * + * Microsoft SQL Server supports this through AUTO_INCREMENT columns. + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsReleaseSavepoints() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function supportsSchemas() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getDefaultSchemaName() + { + return 'dbo'; + } + + /** + * {@inheritDoc} + */ + public function supportsColumnCollation() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function hasNativeGuidType() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getCreateDatabaseSQL($name) + { + return 'CREATE DATABASE ' . $name; + } + + /** + * {@inheritDoc} + */ + public function getDropDatabaseSQL($name) + { + return 'DROP DATABASE ' . $name; + } + + /** + * {@inheritDoc} + */ + public function supportsCreateDropDatabase() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function getCreateSchemaSQL($schemaName) + { + return 'CREATE SCHEMA ' . $schemaName; + } + + /** + * {@inheritDoc} + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + if (! $foreignKey instanceof ForeignKeyConstraint) { + $foreignKey = new Identifier($foreignKey); + } + + if (! $table instanceof Table) { + $table = new Identifier($table); + } + + $foreignKey = $foreignKey->getQuotedName($this); + $table = $table->getQuotedName($this); + + return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey; + } + + /** + * {@inheritDoc} + */ + public function getDropIndexSQL($index, $table = null) + { + if ($index instanceof Index) { + $index = $index->getQuotedName($this); + } elseif (!is_string($index)) { + throw new \InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.'); + } + + if (!isset($table)) { + return 'DROP INDEX ' . $index; + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + return "IF EXISTS (SELECT * FROM sysobjects WHERE name = '$index') + ALTER TABLE " . $table . " DROP CONSTRAINT " . $index . " + ELSE + DROP INDEX " . $index . " ON " . $table; + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) + { + $defaultConstraintsSql = array(); + $commentsSql = array(); + + // @todo does other code breaks because of this? + // force primary keys to be not null + foreach ($columns as &$column) { + if (isset($column['primary']) && $column['primary']) { + $column['notnull'] = true; + } + + // Build default constraints SQL statements. + if (isset($column['default'])) { + $defaultConstraintsSql[] = 'ALTER TABLE ' . $tableName . + ' ADD' . $this->getDefaultConstraintDeclarationSQL($tableName, $column); + } + + if ( ! empty($column['comment']) || is_numeric($column['comment'])) { + $commentsSql[] = $this->getCreateColumnCommentSQL($tableName, $column['name'], $column['comment']); + } + } + + $columnListSql = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['uniqueConstraints']) && !empty($options['uniqueConstraints'])) { + foreach ($options['uniqueConstraints'] as $name => $definition) { + $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition); + } + } + + if (isset($options['primary']) && !empty($options['primary'])) { + $flags = ''; + if (isset($options['primary_index']) && $options['primary_index']->hasFlag('nonclustered')) { + $flags = ' NONCLUSTERED'; + } + $columnListSql .= ', PRIMARY KEY' . $flags . ' (' . implode(', ', array_unique(array_values($options['primary']))) . ')'; + } + + $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql; + + $check = $this->getCheckDeclarationSQL($columns); + if (!empty($check)) { + $query .= ', ' . $check; + } + $query .= ')'; + + $sql[] = $query; + + if (isset($options['indexes']) && !empty($options['indexes'])) { + foreach ($options['indexes'] as $index) { + $sql[] = $this->getCreateIndexSQL($index, $tableName); + } + } + + if (isset($options['foreignKeys'])) { + foreach ((array) $options['foreignKeys'] as $definition) { + $sql[] = $this->getCreateForeignKeySQL($definition, $tableName); + } + } + + return array_merge($sql, $commentsSql, $defaultConstraintsSql); + } + + /** + * {@inheritDoc} + */ + public function getCreatePrimaryKeySQL(Index $index, $table) + { + $flags = ''; + if ($index->hasFlag('nonclustered')) { + $flags = ' NONCLUSTERED'; + } + + return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY' . $flags . ' (' . $this->getIndexFieldDeclarationListSQL($index->getQuotedColumns($this)) . ')'; + } + + /** + * Returns the SQL statement for creating a column comment. + * + * SQL Server does not support native column comments, + * therefore the extended properties functionality is used + * as a workaround to store them. + * The property name used to store column comments is "MS_Description" + * which provides compatibility with SQL Server Management Studio, + * as column comments are stored in the same property there when + * specifying a column's "Description" attribute. + * + * @param string $tableName The quoted table name to which the column belongs. + * @param string $columnName The quoted column name to create the comment for. + * @param string $comment The column's comment. + * + * @return string + */ + protected function getCreateColumnCommentSQL($tableName, $columnName, $comment) + { + return $this->getAddExtendedPropertySQL( + 'MS_Description', + $comment, + 'SCHEMA', + 'dbo', + 'TABLE', + $tableName, + 'COLUMN', + $columnName + ); + } + + /** + * Returns the SQL snippet for declaring a default constraint. + * + * @param string $table Name of the table to return the default constraint declaration for. + * @param array $column Column definition. + * + * @return string + * + * @throws \InvalidArgumentException + */ + public function getDefaultConstraintDeclarationSQL($table, array $column) + { + if ( ! isset($column['default'])) { + throw new \InvalidArgumentException("Incomplete column definition. 'default' required."); + } + + $columnName = new Identifier($column['name']); + + return + ' CONSTRAINT ' . + $this->generateDefaultConstraintName($table, $column['name']) . + $this->getDefaultValueDeclarationSQL($column) . + ' FOR ' . $columnName->getQuotedName($this); + } + + /** + * {@inheritDoc} + */ + public function getUniqueConstraintDeclarationSQL($name, Index $index) + { + $constraint = parent::getUniqueConstraintDeclarationSQL($name, $index); + + $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index); + + return $constraint; + } + + /** + * {@inheritDoc} + */ + public function getCreateIndexSQL(Index $index, $table) + { + $constraint = parent::getCreateIndexSQL($index, $table); + + if ($index->isUnique() && !$index->isPrimary()) { + $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index); + } + + return $constraint; + } + + /** + * {@inheritDoc} + */ + protected function getCreateIndexSQLFlags(Index $index) + { + $type = ''; + if ($index->isUnique()) { + $type .= 'UNIQUE '; + } + + if ($index->hasFlag('clustered')) { + $type .= 'CLUSTERED '; + } elseif ($index->hasFlag('nonclustered')) { + $type .= 'NONCLUSTERED '; + } + + return $type; + } + + /** + * Extend unique key constraint with required filters + * + * @param string $sql + * @param \Doctrine\DBAL\Schema\Index $index + * + * @return string + */ + private function _appendUniqueConstraintDefinition($sql, Index $index) + { + $fields = array(); + + foreach ($index->getQuotedColumns($this) as $field) { + $fields[] = $field . ' IS NOT NULL'; + } + + return $sql . ' WHERE ' . implode(' AND ', $fields); + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $queryParts = array(); + $sql = array(); + $columnSql = array(); + $commentsSql = array(); + + /** @var \Doctrine\DBAL\Schema\Column $column */ + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $columnDef = $column->toArray(); + $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef); + + if (isset($columnDef['default'])) { + $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($diff->name, $column); + } + + $comment = $this->getColumnComment($column); + + if ( ! empty($comment) || is_numeric($comment)) { + $commentsSql[] = $this->getCreateColumnCommentSQL( + $diff->name, + $column->getQuotedName($this), + $comment + ); + } + } + + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this); + } + + /* @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */ + foreach ($diff->changedColumns as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + $column = $columnDiff->column; + $comment = $this->getColumnComment($column); + $hasComment = ! empty ($comment) || is_numeric($comment); + + if ($columnDiff->fromColumn instanceof Column) { + $fromComment = $this->getColumnComment($columnDiff->fromColumn); + $hasFromComment = ! empty ($fromComment) || is_numeric($fromComment); + + if ($hasFromComment && $hasComment && $fromComment != $comment) { + $commentsSql[] = $this->getAlterColumnCommentSQL( + $diff->name, + $column->getQuotedName($this), + $comment + ); + } elseif ($hasFromComment && ! $hasComment) { + $commentsSql[] = $this->getDropColumnCommentSQL($diff->name, $column->getQuotedName($this)); + } elseif ($hasComment) { + $commentsSql[] = $this->getCreateColumnCommentSQL( + $diff->name, + $column->getQuotedName($this), + $comment + ); + } + } else { + // todo: Original comment cannot be determined. What to do? Add, update, drop or skip? + } + + // Do not add query part if only comment has changed. + if ($columnDiff->hasChanged('comment') && count($columnDiff->changedProperties) === 1) { + continue; + } + + $requireDropDefaultConstraint = $this->alterColumnRequiresDropDefaultConstraint($columnDiff); + + if ($requireDropDefaultConstraint) { + $queryParts[] = $this->getAlterTableDropDefaultConstraintClause( + $diff->name, + $columnDiff->oldColumnName + ); + } + + $columnDef = $column->toArray(); + + $queryParts[] = 'ALTER COLUMN ' . + $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef); + + if (isset($columnDef['default']) && ($requireDropDefaultConstraint || $columnDiff->hasChanged('default'))) { + $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($diff->name, $column); + } + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $oldColumnName = new Identifier($oldColumnName); + + $sql[] = "sp_RENAME '" . + $diff->getName($this)->getQuotedName($this) . "." . $oldColumnName->getQuotedName($this) . + "', '" . $column->getQuotedName($this) . "', 'COLUMN'"; + + // Recreate default constraint with new column name if necessary (for future reference). + if ($column->getDefault() !== null) { + $queryParts[] = $this->getAlterTableDropDefaultConstraintClause( + $diff->name, + $oldColumnName->getQuotedName($this) + ); + $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($diff->name, $column); + } + } + + $tableSql = array(); + + if ($this->onSchemaAlterTable($diff, $tableSql)) { + return array_merge($tableSql, $columnSql); + } + + foreach ($queryParts as $query) { + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + + $sql = array_merge($sql, $commentsSql); + + if ($diff->newName !== false) { + $sql[] = "sp_RENAME '" . $diff->getName($this)->getQuotedName($this) . "', '" . $diff->getNewName()->getName() . "'"; + + /** + * Rename table's default constraints names + * to match the new table name. + * This is necessary to ensure that the default + * constraints can be referenced in future table + * alterations as the table name is encoded in + * default constraints' names. + */ + $sql[] = "DECLARE @sql NVARCHAR(MAX) = N''; " . + "SELECT @sql += N'EXEC sp_rename N''' + dc.name + ''', N''' " . + "+ REPLACE(dc.name, '" . $this->generateIdentifierName($diff->name) . "', " . + "'" . $this->generateIdentifierName($diff->newName) . "') + ''', ''OBJECT'';' " . + "FROM sys.default_constraints dc " . + "JOIN sys.tables tbl ON dc.parent_object_id = tbl.object_id " . + "WHERE tbl.name = '" . $diff->getNewName()->getName() . "';" . + "EXEC sp_executesql @sql"; + } + + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff) + ); + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * Returns the SQL clause for adding a default constraint in an ALTER TABLE statement. + * + * @param string $tableName The name of the table to generate the clause for. + * @param Column $column The column to generate the clause for. + * + * @return string + */ + private function getAlterTableAddDefaultConstraintClause($tableName, Column $column) + { + $columnDef = $column->toArray(); + $columnDef['name'] = $column->getQuotedName($this); + + return 'ADD' . $this->getDefaultConstraintDeclarationSQL($tableName, $columnDef); + } + + /** + * Returns the SQL clause for dropping an existing default constraint in an ALTER TABLE statement. + * + * @param string $tableName The name of the table to generate the clause for. + * @param string $columnName The name of the column to generate the clause for. + * + * @return string + */ + private function getAlterTableDropDefaultConstraintClause($tableName, $columnName) + { + return 'DROP CONSTRAINT ' . $this->generateDefaultConstraintName($tableName, $columnName); + } + + /** + * Checks whether a column alteration requires dropping its default constraint first. + * + * Different to other database vendors SQL Server implements column default values + * as constraints and therefore changes in a column's default value as well as changes + * in a column's type require dropping the default constraint first before being to + * alter the particular column to the new definition. + * + * @param ColumnDiff $columnDiff The column diff to evaluate. + * + * @return boolean True if the column alteration requires dropping its default constraint first, false otherwise. + */ + private function alterColumnRequiresDropDefaultConstraint(ColumnDiff $columnDiff) + { + // We can only decide whether to drop an existing default constraint + // if we know the original default value. + if ( ! $columnDiff->fromColumn instanceof Column) { + return false; + } + + // We only need to drop an existing default constraint if we know the + // column was defined with a default value before. + if ($columnDiff->fromColumn->getDefault() === null) { + return false; + } + + // We need to drop an existing default constraint if the column was + // defined with a default value before and it has changed. + if ($columnDiff->hasChanged('default')) { + return true; + } + + // We need to drop an existing default constraint if the column was + // defined with a default value before and the native column type has changed. + if ($columnDiff->hasChanged('type') || $columnDiff->hasChanged('fixed')) { + return true; + } + + return false; + } + + /** + * Returns the SQL statement for altering a column comment. + * + * SQL Server does not support native column comments, + * therefore the extended properties functionality is used + * as a workaround to store them. + * The property name used to store column comments is "MS_Description" + * which provides compatibility with SQL Server Management Studio, + * as column comments are stored in the same property there when + * specifying a column's "Description" attribute. + * + * @param string $tableName The quoted table name to which the column belongs. + * @param string $columnName The quoted column name to alter the comment for. + * @param string $comment The column's comment. + * + * @return string + */ + protected function getAlterColumnCommentSQL($tableName, $columnName, $comment) + { + return $this->getUpdateExtendedPropertySQL( + 'MS_Description', + $comment, + 'SCHEMA', + 'dbo', + 'TABLE', + $tableName, + 'COLUMN', + $columnName + ); + } + + /** + * Returns the SQL statement for dropping a column comment. + * + * SQL Server does not support native column comments, + * therefore the extended properties functionality is used + * as a workaround to store them. + * The property name used to store column comments is "MS_Description" + * which provides compatibility with SQL Server Management Studio, + * as column comments are stored in the same property there when + * specifying a column's "Description" attribute. + * + * @param string $tableName The quoted table name to which the column belongs. + * @param string $columnName The quoted column name to drop the comment for. + * + * @return string + */ + protected function getDropColumnCommentSQL($tableName, $columnName) + { + return $this->getDropExtendedPropertySQL( + 'MS_Description', + 'SCHEMA', + 'dbo', + 'TABLE', + $tableName, + 'COLUMN', + $columnName + ); + } + + /** + * {@inheritdoc} + */ + protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) + { + return array( + sprintf( + "EXEC sp_RENAME N'%s.%s', N'%s', N'INDEX'", + $tableName, + $oldIndexName, + $index->getQuotedName($this) + ) + ); + } + + /** + * Returns the SQL statement for adding an extended property to a database object. + * + * @param string $name The name of the property to add. + * @param string|null $value The value of the property to add. + * @param string|null $level0Type The type of the object at level 0 the property belongs to. + * @param string|null $level0Name The name of the object at level 0 the property belongs to. + * @param string|null $level1Type The type of the object at level 1 the property belongs to. + * @param string|null $level1Name The name of the object at level 1 the property belongs to. + * @param string|null $level2Type The type of the object at level 2 the property belongs to. + * @param string|null $level2Name The name of the object at level 2 the property belongs to. + * + * @return string + * + * @link http://msdn.microsoft.com/en-us/library/ms180047%28v=sql.90%29.aspx + */ + public function getAddExtendedPropertySQL( + $name, + $value = null, + $level0Type = null, + $level0Name = null, + $level1Type = null, + $level1Name = null, + $level2Type = null, + $level2Name = null + ) { + return "EXEC sp_addextendedproperty " . + "N" . $this->quoteStringLiteral($name) . ", N" . $this->quoteStringLiteral($value) . ", " . + "N" . $this->quoteStringLiteral($level0Type) . ", " . $level0Name . ', ' . + "N" . $this->quoteStringLiteral($level1Type) . ", " . $level1Name . ', ' . + "N" . $this->quoteStringLiteral($level2Type) . ", " . $level2Name; + } + + /** + * Returns the SQL statement for dropping an extended property from a database object. + * + * @param string $name The name of the property to drop. + * @param string|null $level0Type The type of the object at level 0 the property belongs to. + * @param string|null $level0Name The name of the object at level 0 the property belongs to. + * @param string|null $level1Type The type of the object at level 1 the property belongs to. + * @param string|null $level1Name The name of the object at level 1 the property belongs to. + * @param string|null $level2Type The type of the object at level 2 the property belongs to. + * @param string|null $level2Name The name of the object at level 2 the property belongs to. + * + * @return string + * + * @link http://technet.microsoft.com/en-gb/library/ms178595%28v=sql.90%29.aspx + */ + public function getDropExtendedPropertySQL( + $name, + $level0Type = null, + $level0Name = null, + $level1Type = null, + $level1Name = null, + $level2Type = null, + $level2Name = null + ) { + return "EXEC sp_dropextendedproperty " . + "N" . $this->quoteStringLiteral($name) . ", " . + "N" . $this->quoteStringLiteral($level0Type) . ", " . $level0Name . ', ' . + "N" . $this->quoteStringLiteral($level1Type) . ", " . $level1Name . ', ' . + "N" . $this->quoteStringLiteral($level2Type) . ", " . $level2Name; + } + + /** + * Returns the SQL statement for updating an extended property of a database object. + * + * @param string $name The name of the property to update. + * @param string|null $value The value of the property to update. + * @param string|null $level0Type The type of the object at level 0 the property belongs to. + * @param string|null $level0Name The name of the object at level 0 the property belongs to. + * @param string|null $level1Type The type of the object at level 1 the property belongs to. + * @param string|null $level1Name The name of the object at level 1 the property belongs to. + * @param string|null $level2Type The type of the object at level 2 the property belongs to. + * @param string|null $level2Name The name of the object at level 2 the property belongs to. + * + * @return string + * + * @link http://msdn.microsoft.com/en-us/library/ms186885%28v=sql.90%29.aspx + */ + public function getUpdateExtendedPropertySQL( + $name, + $value = null, + $level0Type = null, + $level0Name = null, + $level1Type = null, + $level1Name = null, + $level2Type = null, + $level2Name = null + ) { + return "EXEC sp_updateextendedproperty " . + "N" . $this->quoteStringLiteral($name) . ", N" . $this->quoteStringLiteral($value) . ", " . + "N" . $this->quoteStringLiteral($level0Type) . ", " . $level0Name . ', ' . + "N" . $this->quoteStringLiteral($level1Type) . ", " . $level1Name . ', ' . + "N" . $this->quoteStringLiteral($level2Type) . ", " . $level2Name; + } + + /** + * {@inheritDoc} + */ + public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) + { + return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES'; + } + + /** + * {@inheritDoc} + */ + public function getListTablesSQL() + { + // "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams + // Category 2 must be ignored as it is "MS SQL Server 'pseudo-system' object[s]" for replication + return "SELECT name FROM sysobjects WHERE type = 'U' AND name != 'sysdiagrams' AND category != 2 ORDER BY name"; + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + return "SELECT col.name, + type.name AS type, + col.max_length AS length, + ~col.is_nullable AS notnull, + def.definition AS [default], + col.scale, + col.precision, + col.is_identity AS autoincrement, + col.collation_name AS collation, + CAST(prop.value AS NVARCHAR(MAX)) AS comment -- CAST avoids driver error for sql_variant type + FROM sys.columns AS col + JOIN sys.types AS type + ON col.user_type_id = type.user_type_id + JOIN sys.objects AS obj + ON col.object_id = obj.object_id + JOIN sys.schemas AS scm + ON obj.schema_id = scm.schema_id + LEFT JOIN sys.default_constraints def + ON col.default_object_id = def.object_id + AND col.object_id = def.parent_object_id + LEFT JOIN sys.extended_properties AS prop + ON obj.object_id = prop.major_id + AND col.column_id = prop.minor_id + AND prop.name = 'MS_Description' + WHERE obj.type = 'U' + AND " . $this->getTableWhereClause($table, 'scm.name', 'obj.name'); + } + + /** + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table, $database = null) + { + return "SELECT f.name AS ForeignKey, + SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName, + OBJECT_NAME (f.parent_object_id) AS TableName, + COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName, + SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName, + OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName, + COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName, + f.delete_referential_action_desc, + f.update_referential_action_desc + FROM sys.foreign_keys AS f + INNER JOIN sys.foreign_key_columns AS fc + INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id + ON f.OBJECT_ID = fc.constraint_object_id + WHERE " . + $this->getTableWhereClause($table, 'SCHEMA_NAME (f.schema_id)', 'OBJECT_NAME (f.parent_object_id)'); + } + + /** + * {@inheritDoc} + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + return "SELECT idx.name AS key_name, + col.name AS column_name, + ~idx.is_unique AS non_unique, + idx.is_primary_key AS [primary], + CASE idx.type + WHEN '1' THEN 'clustered' + WHEN '2' THEN 'nonclustered' + ELSE NULL + END AS flags + FROM sys.tables AS tbl + JOIN sys.schemas AS scm ON tbl.schema_id = scm.schema_id + JOIN sys.indexes AS idx ON tbl.object_id = idx.object_id + JOIN sys.index_columns AS idxcol ON idx.object_id = idxcol.object_id AND idx.index_id = idxcol.index_id + JOIN sys.columns AS col ON idxcol.object_id = col.object_id AND idxcol.column_id = col.column_id + WHERE " . $this->getTableWhereClause($table, 'scm.name', 'tbl.name') . " + ORDER BY idx.index_id ASC, idxcol.key_ordinal ASC"; + } + + /** + * {@inheritDoc} + */ + public function getCreateViewSQL($name, $sql) + { + return 'CREATE VIEW ' . $name . ' AS ' . $sql; + } + + /** + * {@inheritDoc} + */ + public function getListViewsSQL($database) + { + return "SELECT name FROM sysobjects WHERE type = 'V' ORDER BY name"; + } + + /** + * Returns the where clause to filter schema and table name in a query. + * + * @param string $table The full qualified name of the table. + * @param string $schemaColumn The name of the column to compare the schema to in the where clause. + * @param string $tableColumn The name of the column to compare the table to in the where clause. + * + * @return string + */ + private function getTableWhereClause($table, $schemaColumn, $tableColumn) + { + if (strpos($table, ".") !== false) { + list($schema, $table) = explode(".", $table); + $schema = $this->quoteStringLiteral($schema); + $table = $this->quoteStringLiteral($table); + } else { + $schema = "SCHEMA_NAME()"; + $table = $this->quoteStringLiteral($table); + } + + return "({$tableColumn} = {$table} AND {$schemaColumn} = {$schema})"; + } + + /** + * {@inheritDoc} + */ + public function getDropViewSQL($name) + { + return 'DROP VIEW ' . $name; + } + + /** + * {@inheritDoc} + */ + public function getGuidExpression() + { + return 'NEWID()'; + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'CHARINDEX(' . $substr . ', ' . $str . ')'; + } + + return 'CHARINDEX(' . $substr . ', ' . $str . ', ' . $startPos . ')'; + } + + /** + * {@inheritDoc} + */ + public function getModExpression($expression1, $expression2) + { + return $expression1 . ' % ' . $expression2; + } + + /** + * {@inheritDoc} + */ + public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false) + { + if ( ! $char) { + switch ($pos) { + case self::TRIM_LEADING: + $trimFn = 'LTRIM'; + break; + + case self::TRIM_TRAILING: + $trimFn = 'RTRIM'; + break; + + default: + return 'LTRIM(RTRIM(' . $str . '))'; + } + + return $trimFn . '(' . $str . ')'; + } + + /** Original query used to get those expressions + declare @c varchar(100) = 'xxxBarxxx', @trim_char char(1) = 'x'; + declare @pat varchar(10) = '%[^' + @trim_char + ']%'; + select @c as string + , @trim_char as trim_char + , stuff(@c, 1, patindex(@pat, @c) - 1, null) as trim_leading + , reverse(stuff(reverse(@c), 1, patindex(@pat, reverse(@c)) - 1, null)) as trim_trailing + , reverse(stuff(reverse(stuff(@c, 1, patindex(@pat, @c) - 1, null)), 1, patindex(@pat, reverse(stuff(@c, 1, patindex(@pat, @c) - 1, null))) - 1, null)) as trim_both; + */ + $pattern = "'%[^' + $char + ']%'"; + + if ($pos == self::TRIM_LEADING) { + return 'stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)'; + } + + if ($pos == self::TRIM_TRAILING) { + return 'reverse(stuff(reverse(' . $str . '), 1, patindex(' . $pattern . ', reverse(' . $str . ')) - 1, null))'; + } + + return 'reverse(stuff(reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)), 1, patindex(' . $pattern . ', reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null))) - 1, null))'; + } + + /** + * {@inheritDoc} + */ + public function getConcatExpression() + { + $args = func_get_args(); + + return '(' . implode(' + ', $args) . ')'; + } + + /** + * {@inheritDoc} + */ + public function getListDatabasesSQL() + { + return 'SELECT * FROM sys.databases'; + } + + /** + * {@inheritDoc} + */ + public function getListNamespacesSQL() + { + return "SELECT name FROM sys.schemas WHERE name NOT IN('guest', 'INFORMATION_SCHEMA', 'sys')"; + } + + /** + * {@inheritDoc} + */ + public function getSubstringExpression($value, $from, $length = null) + { + if (!is_null($length)) { + return 'SUBSTRING(' . $value . ', ' . $from . ', ' . $length . ')'; + } + + return 'SUBSTRING(' . $value . ', ' . $from . ', LEN(' . $value . ') - ' . $from . ' + 1)'; + } + + /** + * {@inheritDoc} + */ + public function getLengthExpression($column) + { + return 'LEN(' . $column . ')'; + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $field) + { + return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $field) + { + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $field) + { + return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getGuidTypeDeclarationSQL(array $field) + { + return 'UNIQUEIDENTIFIER'; + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? ($length ? 'NCHAR(' . $length . ')' : 'CHAR(255)') : ($length ? 'NVARCHAR(' . $length . ')' : 'NVARCHAR(255)'); + } + + /** + * {@inheritdoc} + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? 'BINARY(' . ($length ?: 255) . ')' : 'VARBINARY(' . ($length ?: 255) . ')'; + } + + /** + * {@inheritdoc} + */ + public function getBinaryMaxLength() + { + return 8000; + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + return 'VARCHAR(MAX)'; + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + return (!empty($columnDef['autoincrement'])) ? ' IDENTITY' : ''; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATETIME'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATETIME'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATETIME'; + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $field) + { + return 'BIT'; + } + + /** + * {@inheritDoc} + */ + protected function doModifyLimitQuery($query, $limit, $offset = null) + { + if ($limit === null) { + return $query; + } + + $start = $offset + 1; + $end = $offset + $limit; + + // We'll find a SELECT or SELECT distinct and prepend TOP n to it + // Even if the TOP n is very large, the use of a CTE will + // allow the SQL Server query planner to optimize it so it doesn't + // actually scan the entire range covered by the TOP clause. + $selectPattern = '/^(\s*SELECT\s+(?:DISTINCT\s+)?)(.*)$/i'; + $replacePattern = sprintf('$1%s $2', "TOP $end"); + $query = preg_replace($selectPattern, $replacePattern, $query); + + if (stristr($query, "ORDER BY")) { + // Inner order by is not valid in SQL Server for our purposes + // unless it's in a TOP N subquery. + $query = $this->scrubInnerOrderBy($query); + } + + // Build a new limited query around the original, using a CTE + return sprintf( + "WITH dctrn_cte AS (%s) " + . "SELECT * FROM (" + . "SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS doctrine_rownum FROM dctrn_cte" + . ") AS doctrine_tbl " + . "WHERE doctrine_rownum BETWEEN %d AND %d ORDER BY doctrine_rownum ASC", + $query, + $start, + $end + ); + } + + /** + * Remove ORDER BY clauses in subqueries - they're not supported by SQL Server. + * Caveat: will leave ORDER BY in TOP N subqueries. + * + * @param $query + * @return string + */ + private function scrubInnerOrderBy($query) + { + $count = substr_count(strtoupper($query), 'ORDER BY'); + $offset = 0; + + while ($count-- > 0) { + $orderByPos = stripos($query, ' ORDER BY', $offset); + if ($orderByPos === false) { + break; + } + + $qLen = strlen($query); + $parenCount = 0; + $currentPosition = $orderByPos; + + while ($parenCount >= 0 && $currentPosition < $qLen) { + if ($query[$currentPosition] === '(') { + $parenCount++; + } elseif ($query[$currentPosition] === ')') { + $parenCount--; + } + + $currentPosition++; + } + + if ($this->isOrderByInTopNSubquery($query, $orderByPos)) { + // If the order by clause is in a TOP N subquery, do not remove + // it and continue iteration from the current position. + $offset = $currentPosition; + continue; + } + + if ($currentPosition < $qLen - 1) { + $query = substr($query, 0, $orderByPos) . substr($query, $currentPosition - 1); + $offset = $orderByPos; + } + } + return $query; + } + + /** + * Check an ORDER BY clause to see if it is in a TOP N query or subquery. + * + * @param string $query The query + * @param int $currentPosition Start position of ORDER BY clause + * @return bool true if ORDER BY is in a TOP N query, false otherwise + */ + private function isOrderByInTopNSubquery($query, $currentPosition) + { + // Grab query text on the same nesting level as the ORDER BY clause we're examining. + $subQueryBuffer = ''; + $parenCount = 0; + + // If $parenCount goes negative, we've exited the subquery we're examining. + // If $currentPosition goes negative, we've reached the beginning of the query. + while ($parenCount >= 0 && $currentPosition >= 0) { + if ($query[$currentPosition] === '(') { + $parenCount--; + } elseif ($query[$currentPosition] === ')') { + $parenCount++; + } + + // Only yank query text on the same nesting level as the ORDER BY clause. + $subQueryBuffer = ($parenCount === 0 ? $query[$currentPosition] : ' ') . $subQueryBuffer; + + $currentPosition--; + } + + if (preg_match('/SELECT\s+(DISTINCT\s+)?TOP\s/i', $subQueryBuffer)) { + return true; + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function supportsLimitOffset() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function convertBooleans($item) + { + if (is_array($item)) { + foreach ($item as $key => $value) { + if (is_bool($value) || is_numeric($item)) { + $item[$key] = ($value) ? 1 : 0; + } + } + } elseif (is_bool($item) || is_numeric($item)) { + $item = ($item) ? 1 : 0; + } + + return $item; + } + + /** + * {@inheritDoc} + */ + public function getCreateTemporaryTableSnippetSQL() + { + return "CREATE TABLE"; + } + + /** + * {@inheritDoc} + */ + public function getTemporaryTableName($tableName) + { + return '#' . $tableName; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeFormatString() + { + return 'Y-m-d H:i:s.000'; + } + + /** + * {@inheritDoc} + */ + public function getDateFormatString() + { + return 'Y-m-d H:i:s.000'; + } + + /** + * {@inheritDoc} + */ + public function getTimeFormatString() + { + return 'Y-m-d H:i:s.000'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzFormatString() + { + return $this->getDateTimeFormatString(); + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'mssql'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'bigint' => 'bigint', + 'numeric' => 'decimal', + 'bit' => 'boolean', + 'smallint' => 'smallint', + 'decimal' => 'decimal', + 'smallmoney' => 'integer', + 'int' => 'integer', + 'tinyint' => 'smallint', + 'money' => 'integer', + 'float' => 'float', + 'real' => 'float', + 'double' => 'float', + 'double precision' => 'float', + 'smalldatetime' => 'datetime', + 'datetime' => 'datetime', + 'char' => 'string', + 'varchar' => 'string', + 'text' => 'text', + 'nchar' => 'string', + 'nvarchar' => 'string', + 'ntext' => 'text', + 'binary' => 'binary', + 'varbinary' => 'binary', + 'image' => 'blob', + 'uniqueidentifier' => 'guid', + ); + } + + /** + * {@inheritDoc} + */ + public function createSavePoint($savepoint) + { + return 'SAVE TRANSACTION ' . $savepoint; + } + + /** + * {@inheritDoc} + */ + public function releaseSavePoint($savepoint) + { + return ''; + } + + /** + * {@inheritDoc} + */ + public function rollbackSavePoint($savepoint) + { + return 'ROLLBACK TRANSACTION ' . $savepoint; + } + + /** + * {@inheritdoc} + */ + public function getForeignKeyReferentialActionSQL($action) + { + // RESTRICT is not supported, therefore falling back to NO ACTION. + if (strtoupper($action) === 'RESTRICT') { + return 'NO ACTION'; + } + + return parent::getForeignKeyReferentialActionSQL($action); + } + + /** + * {@inheritDoc} + */ + public function appendLockHint($fromClause, $lockMode) + { + switch (true) { + case LockMode::NONE === $lockMode: + return $fromClause . ' WITH (NOLOCK)'; + + case LockMode::PESSIMISTIC_READ === $lockMode: + return $fromClause . ' WITH (HOLDLOCK, ROWLOCK)'; + + case LockMode::PESSIMISTIC_WRITE === $lockMode: + return $fromClause . ' WITH (UPDLOCK, ROWLOCK)'; + + default: + return $fromClause; + } + } + + /** + * {@inheritDoc} + */ + public function getForUpdateSQL() + { + return ' '; + } + + /** + * {@inheritDoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLServerKeywords'; + } + + /** + * {@inheritDoc} + */ + public function quoteSingleIdentifier($str) + { + return "[" . str_replace("]", "][", $str) . "]"; + } + + /** + * {@inheritDoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + $tableIdentifier = new Identifier($tableName); + + return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this); + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $field) + { + return 'VARBINARY(MAX)'; + } + + /** + * {@inheritDoc} + */ + public function getDefaultValueDeclarationSQL($field) + { + if ( ! isset($field['default'])) { + return empty($field['notnull']) ? ' NULL' : ''; + } + + if ( ! isset($field['type'])) { + return " DEFAULT '" . $field['default'] . "'"; + } + + if (in_array((string) $field['type'], array('Integer', 'BigInt', 'SmallInt'))) { + return " DEFAULT " . $field['default']; + } + + if (in_array((string) $field['type'], array('DateTime', 'DateTimeTz')) && $field['default'] == $this->getCurrentTimestampSQL()) { + return " DEFAULT " . $this->getCurrentTimestampSQL(); + } + + if ((string) $field['type'] == 'Boolean') { + return " DEFAULT '" . $this->convertBooleans($field['default']) . "'"; + } + + return " DEFAULT '" . $field['default'] . "'"; + } + + /** + * {@inheritdoc} + * + * Modifies column declaration order as it differs in Microsoft SQL Server. + */ + public function getColumnDeclarationSQL($name, array $field) + { + if (isset($field['columnDefinition'])) { + $columnDef = $this->getCustomTypeDeclarationSQL($field); + } else { + $collation = (isset($field['collation']) && $field['collation']) ? + ' ' . $this->getColumnCollationDeclarationSQL($field['collation']) : ''; + + $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : ''; + + $unique = (isset($field['unique']) && $field['unique']) ? + ' ' . $this->getUniqueFieldDeclarationSQL() : ''; + + $check = (isset($field['check']) && $field['check']) ? + ' ' . $field['check'] : ''; + + $typeDecl = $field['type']->getSqlDeclaration($field, $this); + $columnDef = $typeDecl . $collation . $notnull . $unique . $check; + } + + return $name . ' ' . $columnDef; + } + + /** + * Returns a unique default constraint name for a table and column. + * + * @param string $table Name of the table to generate the unique default constraint name for. + * @param string $column Name of the column in the table to generate the unique default constraint name for. + * + * @return string + */ + private function generateDefaultConstraintName($table, $column) + { + return 'DF_' . $this->generateIdentifierName($table) . '_' . $this->generateIdentifierName($column); + } + + /** + * Returns a hash value for a given identifier. + * + * @param string $identifier Identifier to generate a hash value for. + * + * @return string + */ + private function generateIdentifierName($identifier) + { + // Always generate name for unquoted identifiers to ensure consistency. + $identifier = new Identifier($identifier); + + return strtoupper(dechex(crc32($identifier->getName()))); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php new file mode 100644 index 00000000..4711d99d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php @@ -0,0 +1,1118 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Identifier; +use Doctrine\DBAL\Schema\Constraint; + +/** + * The SqlitePlatform class describes the specifics and dialects of the SQLite + * database platform. + * + * @since 2.0 + * @author Roman Borschel + * @author Benjamin Eberlei + * @author Martin Hasoň + * @todo Rename: SQLitePlatform + */ +class SqlitePlatform extends AbstractPlatform +{ + /** + * {@inheritDoc} + */ + public function getRegexpExpression() + { + return 'REGEXP'; + } + + /** + * {@inheritDoc} + */ + public function getGuidExpression() + { + return "HEX(RANDOMBLOB(4)) || '-' || HEX(RANDOMBLOB(2)) || '-4' || " + . "SUBSTR(HEX(RANDOMBLOB(2)), 2) || '-' || " + . "SUBSTR('89AB', 1 + (ABS(RANDOM()) % 4), 1) || " + . "SUBSTR(HEX(RANDOMBLOB(2)), 2) || '-' || HEX(RANDOMBLOB(6))"; + } + + /** + * {@inheritDoc} + */ + public function getNowExpression($type = 'timestamp') + { + switch ($type) { + case 'time': + return 'time(\'now\')'; + case 'date': + return 'date(\'now\')'; + case 'timestamp': + default: + return 'datetime(\'now\')'; + } + } + + /** + * {@inheritDoc} + */ + public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false) + { + $trimChar = ($char != false) ? (', ' . $char) : ''; + + switch ($pos) { + case self::TRIM_LEADING: + $trimFn = 'LTRIM'; + break; + + case self::TRIM_TRAILING: + $trimFn = 'RTRIM'; + break; + + default: + $trimFn = 'TRIM'; + } + + return $trimFn . '(' . $str . $trimChar . ')'; + } + + /** + * {@inheritDoc} + * + * SQLite only supports the 2 parameter variant of this function + */ + public function getSubstringExpression($value, $position, $length = null) + { + if ($length !== null) { + return 'SUBSTR(' . $value . ', ' . $position . ', ' . $length . ')'; + } + + return 'SUBSTR(' . $value . ', ' . $position . ', LENGTH(' . $value . '))'; + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'LOCATE('.$str.', '.$substr.')'; + } + + return 'LOCATE('.$str.', '.$substr.', '.$startPos.')'; + } + + /** + * {@inheritdoc} + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + switch ($unit) { + case self::DATE_INTERVAL_UNIT_SECOND: + case self::DATE_INTERVAL_UNIT_MINUTE: + case self::DATE_INTERVAL_UNIT_HOUR: + return "DATETIME(" . $date . ",'" . $operator . $interval . " " . $unit . "')"; + + default: + switch ($unit) { + case self::DATE_INTERVAL_UNIT_WEEK: + $interval *= 7; + $unit = self::DATE_INTERVAL_UNIT_DAY; + break; + + case self::DATE_INTERVAL_UNIT_QUARTER: + $interval *= 3; + $unit = self::DATE_INTERVAL_UNIT_MONTH; + break; + } + + return "DATE(" . $date . ",'" . $operator . $interval . " " . $unit . "')"; + } + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return 'ROUND(JULIANDAY('.$date1 . ')-JULIANDAY('.$date2.'))'; + } + + /** + * {@inheritDoc} + */ + protected function _getTransactionIsolationLevelSQL($level) + { + switch ($level) { + case \Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED: + return 0; + case \Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED: + case \Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ: + case \Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE: + return 1; + default: + return parent::_getTransactionIsolationLevelSQL($level); + } + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + */ + public function prefersIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $field) + { + return 'BOOLEAN'; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $field) + { + return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $field) + { + // SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields. + if ( ! empty($field['autoincrement'])) { + return $this->getIntegerTypeDeclarationSQL($field); + } + + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getTinyIntTypeDeclarationSql(array $field) + { + // SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT fields. + if ( ! empty($field['autoincrement'])) { + return $this->getIntegerTypeDeclarationSQL($field); + } + + return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $field) + { + // SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields. + if ( ! empty($field['autoincrement'])) { + return $this->getIntegerTypeDeclarationSQL($field); + } + + return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getMediumIntTypeDeclarationSql(array $field) + { + // SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT fields. + if ( ! empty($field['autoincrement'])) { + return $this->getIntegerTypeDeclarationSQL($field); + } + + return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATETIME'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIME'; + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + // sqlite autoincrement is implicit for integer PKs, but not when the field is unsigned + if ( ! empty($columnDef['autoincrement'])) { + return ''; + } + + return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : ''; + } + + /** + * {@inheritDoc} + */ + public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) + { + return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint( + $foreignKey->getQuotedLocalColumns($this), + str_replace('.', '__', $foreignKey->getQuotedForeignTableName($this)), + $foreignKey->getQuotedForeignColumns($this), + $foreignKey->getName(), + $foreignKey->getOptions() + )); + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($name, array $columns, array $options = array()) + { + $name = str_replace('.', '__', $name); + $queryFields = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + foreach ($options['uniqueConstraints'] as $name => $definition) { + $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition); + } + } + + if (isset($options['primary']) && ! empty($options['primary'])) { + $keyColumns = array_unique(array_values($options['primary'])); + $queryFields.= ', PRIMARY KEY('.implode(', ', $keyColumns).')'; + } + + if (isset($options['foreignKeys'])) { + foreach ($options['foreignKeys'] as $foreignKey) { + $queryFields.= ', '.$this->getForeignKeyDeclarationSQL($foreignKey); + } + } + + $query[] = 'CREATE TABLE ' . $name . ' (' . $queryFields . ')'; + + if (isset($options['alter']) && true === $options['alter']) { + return $query; + } + + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach ($options['indexes'] as $indexDef) { + $query[] = $this->getCreateIndexSQL($indexDef, $name); + } + } + + if (isset($options['unique']) && ! empty($options['unique'])) { + foreach ($options['unique'] as $indexDef) { + $query[] = $this->getCreateIndexSQL($indexDef, $name); + } + } + + return $query; + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') + : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT'); + } + + /** + * {@inheritdoc} + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) + { + return 'BLOB'; + } + + /** + * {@inheritdoc} + */ + public function getBinaryMaxLength() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function getBinaryDefaultLength() + { + return 0; + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + return 'CLOB'; + } + + /** + * {@inheritDoc} + */ + public function getListTableConstraintsSQL($table) + { + $table = str_replace('.', '__', $table); + $table = $this->quoteStringLiteral($table); + + return "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = $table AND sql NOT NULL ORDER BY name"; + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $currentDatabase = null) + { + $table = str_replace('.', '__', $table); + $table = $this->quoteStringLiteral($table); + + return "PRAGMA table_info($table)"; + } + + /** + * {@inheritDoc} + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + $table = str_replace('.', '__', $table); + $table = $this->quoteStringLiteral($table); + + return "PRAGMA index_list($table)"; + } + + /** + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return "SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence' AND name != 'geometry_columns' AND name != 'spatial_ref_sys' " + . "UNION ALL SELECT name FROM sqlite_temp_master " + . "WHERE type = 'table' ORDER BY name"; + } + + /** + * {@inheritDoc} + */ + public function getListViewsSQL($database) + { + return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL"; + } + + /** + * {@inheritDoc} + */ + public function getCreateViewSQL($name, $sql) + { + return 'CREATE VIEW ' . $name . ' AS ' . $sql; + } + + /** + * {@inheritDoc} + */ + public function getDropViewSQL($name) + { + return 'DROP VIEW '. $name; + } + + /** + * {@inheritDoc} + */ + public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) + { + $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey); + + $query .= (($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) ? ' ' : ' NOT ') . 'DEFERRABLE'; + $query .= ' INITIALLY ' . (($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) ? 'DEFERRED' : 'IMMEDIATE'); + + return $query; + } + + /** + * {@inheritDoc} + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsColumnCollation() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'sqlite'; + } + + /** + * {@inheritDoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + $tableIdentifier = new Identifier($tableName); + $tableName = str_replace('.', '__', $tableIdentifier->getQuotedName($this)); + + return 'DELETE FROM ' . $tableName; + } + + /** + * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction(). + * + * @param integer|float $value + * + * @return float + */ + static public function udfSqrt($value) + { + return sqrt($value); + } + + /** + * User-defined function for Sqlite that implements MOD(a, b). + * + * @param integer $a + * @param integer $b + * + * @return integer + */ + static public function udfMod($a, $b) + { + return ($a % $b); + } + + /** + * @param string $str + * @param string $substr + * @param integer $offset + * + * @return integer + */ + static public function udfLocate($str, $substr, $offset = 0) + { + // SQL's LOCATE function works on 1-based positions, while PHP's strpos works on 0-based positions. + // So we have to make them compatible if an offset is given. + if ($offset > 0) { + $offset -= 1; + } + + $pos = strpos($str, $substr, $offset); + + if ($pos !== false) { + return $pos + 1; + } + + return 0; + } + + /** + * {@inheritDoc} + */ + public function getForUpdateSql() + { + return ''; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'boolean' => 'boolean', + 'tinyint' => 'boolean', + 'smallint' => 'smallint', + 'mediumint' => 'integer', + 'int' => 'integer', + 'integer' => 'integer', + 'serial' => 'integer', + 'bigint' => 'bigint', + 'bigserial' => 'bigint', + 'clob' => 'text', + 'tinytext' => 'text', + 'mediumtext' => 'text', + 'longtext' => 'text', + 'text' => 'text', + 'varchar' => 'string', + 'longvarchar' => 'string', + 'varchar2' => 'string', + 'nvarchar' => 'string', + 'image' => 'string', + 'ntext' => 'string', + 'char' => 'string', + 'date' => 'date', + 'datetime' => 'datetime', + 'timestamp' => 'datetime', + 'time' => 'time', + 'float' => 'float', + 'double' => 'float', + 'double precision' => 'float', + 'real' => 'float', + 'decimal' => 'decimal', + 'numeric' => 'decimal', + 'blob' => 'blob', + ); + } + + /** + * {@inheritDoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords'; + } + + /** + * {@inheritDoc} + */ + protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) + { + if ( ! $diff->fromTable instanceof Table) { + throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema'); + } + + $sql = array(); + foreach ($diff->fromTable->getIndexes() as $index) { + if ( ! $index->isPrimary()) { + $sql[] = $this->getDropIndexSQL($index, $diff->name); + } + } + + return $sql; + } + + /** + * {@inheritDoc} + */ + protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) + { + if ( ! $diff->fromTable instanceof Table) { + throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema'); + } + + $sql = array(); + $tableName = $diff->newName ? $diff->getNewName(): $diff->getName($this); + foreach ($this->getIndexesInAlteredTable($diff) as $index) { + if ($index->isPrimary()) { + continue; + } + + $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this)); + } + + return $sql; + } + + /** + * {@inheritDoc} + */ + protected function doModifyLimitQuery($query, $limit, $offset) + { + if (null === $limit && null !== $offset) { + return $query . ' LIMIT -1 OFFSET ' . $offset; + } + + return parent::doModifyLimitQuery($query, $limit, $offset); + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $field) + { + return 'BLOB'; + } + + /** + * {@inheritDoc} + */ + public function getTemporaryTableName($tableName) + { + $tableName = str_replace('.', '__', $tableName); + + return $tableName; + } + + /** + * {@inheritDoc} + * + * Sqlite Platform emulates schema by underscoring each dot and generating tables + * into the default database. + * + * This hack is implemented to be able to use SQLite as testdriver when + * using schema supporting databases. + */ + public function canEmulateSchemas() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsForeignKeyConstraints() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function getCreatePrimaryKeySQL(Index $index, $table) + { + throw new DBALException('Sqlite platform does not support alter primary key.'); + } + + /** + * {@inheritdoc} + */ + public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) + { + throw new DBALException('Sqlite platform does not support alter foreign key.'); + } + + /** + * {@inheritdoc} + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + throw new DBALException('Sqlite platform does not support alter foreign key.'); + } + + /** + * {@inheritDoc} + */ + public function getCreateConstraintSQL(Constraint $constraint, $table) + { + throw new DBALException('Sqlite platform does not support alter constraint.'); + } + + /** + * {@inheritDoc} + */ + public function getCreateTableSQL(Table $table, $createFlags = null) + { + $createFlags = null === $createFlags ? self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS : $createFlags; + + return parent::getCreateTableSQL($table, $createFlags); + } + + /** + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table, $database = null) + { + $table = str_replace('.', '__', $table); + $table = $this->quoteStringLiteral($table); + + return "PRAGMA foreign_key_list($table)"; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = $this->getSimpleAlterTableSQL($diff); + if (false !== $sql) { + return $sql; + } + + $fromTable = $diff->fromTable; + if ( ! $fromTable instanceof Table) { + throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema'); + } + + $table = clone $fromTable; + + $columns = array(); + $oldColumnNames = array(); + $newColumnNames = array(); + $columnSql = array(); + + foreach ($table->getColumns() as $columnName => $column) { + $columnName = strtolower($columnName); + $columns[$columnName] = $column; + $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this); + } + + foreach ($diff->removedColumns as $columnName => $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $columnName = strtolower($columnName); + if (isset($columns[$columnName])) { + unset($columns[$columnName]); + unset($oldColumnNames[$columnName]); + unset($newColumnNames[$columnName]); + } + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $oldColumnName = strtolower($oldColumnName); + if (isset($columns[$oldColumnName])) { + unset($columns[$oldColumnName]); + } + + $columns[strtolower($column->getName())] = $column; + + if (isset($newColumnNames[$oldColumnName])) { + $newColumnNames[$oldColumnName] = $column->getQuotedName($this); + } + } + + foreach ($diff->changedColumns as $oldColumnName => $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + if (isset($columns[$oldColumnName])) { + unset($columns[$oldColumnName]); + } + + $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column; + + if (isset($newColumnNames[$oldColumnName])) { + $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this); + } + } + + foreach ($diff->addedColumns as $columnName => $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $columns[strtolower($columnName)] = $column; + } + + $sql = array(); + $tableSql = array(); + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + $dataTable = new Table('__temp__'.$table->getName()); + + $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff), $this->getForeignKeysInAlteredTable($diff), 0, $table->getOptions()); + $newTable->addOption('alter', true); + + $sql = $this->getPreAlterTableIndexForeignKeySQL($diff); + //$sql = array_merge($sql, $this->getCreateTableSQL($dataTable, 0)); + $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this)); + $sql[] = $this->getDropTableSQL($fromTable); + + $sql = array_merge($sql, $this->getCreateTableSQL($newTable)); + $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this)); + $sql[] = $this->getDropTableSQL($dataTable); + + if ($diff->newName && $diff->newName != $diff->name) { + $renamedTable = $diff->getNewName(); + $sql[] = 'ALTER TABLE '.$newTable->getQuotedName($this).' RENAME TO '.$renamedTable->getQuotedName($this); + } + + $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff)); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array|bool + */ + private function getSimpleAlterTableSQL(TableDiff $diff) + { + // Suppress changes on integer type autoincrement columns. + foreach ($diff->changedColumns as $oldColumnName => $columnDiff) { + if ( ! $columnDiff->fromColumn instanceof Column || + ! $columnDiff->column instanceof Column || + ! $columnDiff->column->getAutoincrement() || + ! (string) $columnDiff->column->getType() === 'Integer' + ) { + continue; + } + + if ( ! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) { + unset($diff->changedColumns[$oldColumnName]); + + continue; + } + + $fromColumnType = (string) $columnDiff->fromColumn->getType(); + + if ($fromColumnType === 'SmallInt' || $fromColumnType === 'BigInt') { + unset($diff->changedColumns[$oldColumnName]); + } + } + + if ( ! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes) + || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes) + || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes) + || ! empty($diff->renamedIndexes) + ) { + return false; + } + + $table = new Table($diff->name); + + $sql = array(); + $tableSql = array(); + $columnSql = array(); + + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $field = array_merge(array('unique' => null, 'autoincrement' => null, 'default' => null), $column->toArray()); + $type = (string) $field['type']; + switch (true) { + case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']: + case $type == 'DateTime' && $field['default'] == $this->getCurrentTimestampSQL(): + case $type == 'Date' && $field['default'] == $this->getCurrentDateSQL(): + case $type == 'Time' && $field['default'] == $this->getCurrentTimeSQL(): + return false; + } + + $field['name'] = $column->getQuotedName($this); + if (strtolower($field['type']) == 'string' && $field['length'] === null) { + $field['length'] = 255; + } + + $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' ADD COLUMN '.$this->getColumnDeclarationSQL($field['name'], $field); + } + + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + if ($diff->newName !== false) { + $newTable = new Identifier($diff->newName); + $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' RENAME TO '.$newTable->getQuotedName($this); + } + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array + */ + private function getColumnNamesInAlteredTable(TableDiff $diff) + { + $columns = array(); + + foreach ($diff->fromTable->getColumns() as $columnName => $column) { + $columns[strtolower($columnName)] = $column->getName(); + } + + foreach ($diff->removedColumns as $columnName => $column) { + $columnName = strtolower($columnName); + if (isset($columns[$columnName])) { + unset($columns[$columnName]); + } + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + $columnName = $column->getName(); + $columns[strtolower($oldColumnName)] = $columnName; + $columns[strtolower($columnName)] = $columnName; + } + + foreach ($diff->changedColumns as $oldColumnName => $columnDiff) { + $columnName = $columnDiff->column->getName(); + $columns[strtolower($oldColumnName)] = $columnName; + $columns[strtolower($columnName)] = $columnName; + } + + foreach ($diff->addedColumns as $columnName => $column) { + $columns[strtolower($columnName)] = $columnName; + } + + return $columns; + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return \Doctrine\DBAL\Schema\Index[] + */ + private function getIndexesInAlteredTable(TableDiff $diff) + { + $indexes = $diff->fromTable->getIndexes(); + $columnNames = $this->getColumnNamesInAlteredTable($diff); + + foreach ($indexes as $key => $index) { + foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) { + if (strtolower($key) === strtolower($oldIndexName)) { + unset($indexes[$key]); + } + } + + $changed = false; + $indexColumns = array(); + foreach ($index->getColumns() as $columnName) { + $normalizedColumnName = strtolower($columnName); + if ( ! isset($columnNames[$normalizedColumnName])) { + unset($indexes[$key]); + continue 2; + } else { + $indexColumns[] = $columnNames[$normalizedColumnName]; + if ($columnName !== $columnNames[$normalizedColumnName]) { + $changed = true; + } + } + } + + if ($changed) { + $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags()); + } + } + + foreach ($diff->removedIndexes as $index) { + $indexName = strtolower($index->getName()); + if (strlen($indexName) && isset($indexes[$indexName])) { + unset($indexes[$indexName]); + } + } + + foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) { + $indexName = strtolower($index->getName()); + if (strlen($indexName)) { + $indexes[$indexName] = $index; + } else { + $indexes[] = $index; + } + } + + return $indexes; + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array + */ + private function getForeignKeysInAlteredTable(TableDiff $diff) + { + $foreignKeys = $diff->fromTable->getForeignKeys(); + $columnNames = $this->getColumnNamesInAlteredTable($diff); + + foreach ($foreignKeys as $key => $constraint) { + $changed = false; + $localColumns = array(); + foreach ($constraint->getLocalColumns() as $columnName) { + $normalizedColumnName = strtolower($columnName); + if ( ! isset($columnNames[$normalizedColumnName])) { + unset($foreignKeys[$key]); + continue 2; + } else { + $localColumns[] = $columnNames[$normalizedColumnName]; + if ($columnName !== $columnNames[$normalizedColumnName]) { + $changed = true; + } + } + } + + if ($changed) { + $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions()); + } + } + + foreach ($diff->removedForeignKeys as $constraint) { + $constraintName = strtolower($constraint->getName()); + if (strlen($constraintName) && isset($foreignKeys[$constraintName])) { + unset($foreignKeys[$constraintName]); + } + } + + foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) { + $constraintName = strtolower($constraint->getName()); + if (strlen($constraintName)) { + $foreignKeys[$constraintName] = $constraint; + } else { + $foreignKeys[] = $constraint; + } + } + + return $foreignKeys; + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array + */ + private function getPrimaryIndexInAlteredTable(TableDiff $diff) + { + $primaryIndex = array(); + + foreach ($this->getIndexesInAlteredTable($diff) as $index) { + if ($index->isPrimary()) { + $primaryIndex = array($index->getName() => $index); + } + } + + return $primaryIndex; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Portability/Connection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Portability/Connection.php new file mode 100644 index 00000000..df6b957a --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Portability/Connection.php @@ -0,0 +1,150 @@ +. + */ + +namespace Doctrine\DBAL\Portability; + +use Doctrine\DBAL\Cache\QueryCacheProfile; + +/** + * Portability wrapper for a Connection. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class Connection extends \Doctrine\DBAL\Connection +{ + const PORTABILITY_ALL = 255; + const PORTABILITY_NONE = 0; + const PORTABILITY_RTRIM = 1; + const PORTABILITY_EMPTY_TO_NULL = 4; + const PORTABILITY_FIX_CASE = 8; + + const PORTABILITY_DB2 = 13; + const PORTABILITY_ORACLE = 9; + const PORTABILITY_POSTGRESQL = 13; + const PORTABILITY_SQLITE = 13; + const PORTABILITY_OTHERVENDORS = 12; + const PORTABILITY_DRIZZLE = 13; + const PORTABILITY_SQLANYWHERE = 13; + const PORTABILITY_SQLSRV = 13; + + /** + * @var integer + */ + private $portability = self::PORTABILITY_NONE; + + /** + * @var integer + */ + private $case; + + /** + * {@inheritdoc} + */ + public function connect() + { + $ret = parent::connect(); + if ($ret) { + $params = $this->getParams(); + if (isset($params['portability'])) { + if ($this->getDatabasePlatform()->getName() === "oracle") { + $params['portability'] = $params['portability'] & self::PORTABILITY_ORACLE; + } elseif ($this->getDatabasePlatform()->getName() === "postgresql") { + $params['portability'] = $params['portability'] & self::PORTABILITY_POSTGRESQL; + } elseif ($this->getDatabasePlatform()->getName() === "sqlite") { + $params['portability'] = $params['portability'] & self::PORTABILITY_SQLITE; + } elseif ($this->getDatabasePlatform()->getName() === "drizzle") { + $params['portability'] = self::PORTABILITY_DRIZZLE; + } elseif ($this->getDatabasePlatform()->getName() === 'sqlanywhere') { + $params['portability'] = self::PORTABILITY_SQLANYWHERE; + } elseif ($this->getDatabasePlatform()->getName() === 'db2') { + $params['portability'] = self::PORTABILITY_DB2; + } elseif ($this->getDatabasePlatform()->getName() === 'mssql') { + $params['portability'] = $params['portability'] & self::PORTABILITY_SQLSRV; + } else { + $params['portability'] = $params['portability'] & self::PORTABILITY_OTHERVENDORS; + } + $this->portability = $params['portability']; + } + if (isset($params['fetch_case']) && $this->portability & self::PORTABILITY_FIX_CASE) { + if ($this->_conn instanceof \Doctrine\DBAL\Driver\PDOConnection) { + // make use of c-level support for case handling + $this->_conn->setAttribute(\PDO::ATTR_CASE, $params['fetch_case']); + } else { + $this->case = ($params['fetch_case'] == \PDO::CASE_LOWER) ? CASE_LOWER : CASE_UPPER; + } + } + } + + return $ret; + } + + /** + * @return integer + */ + public function getPortability() + { + return $this->portability; + } + + /** + * @return integer + */ + public function getFetchCase() + { + return $this->case; + } + + /** + * {@inheritdoc} + */ + public function executeQuery($query, array $params = array(), $types = array(), QueryCacheProfile $qcp = null) + { + $stmt = new Statement(parent::executeQuery($query, $params, $types, $qcp), $this); + $stmt->setFetchMode($this->defaultFetchMode); + + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function prepare($statement) + { + $stmt = new Statement(parent::prepare($statement), $this); + $stmt->setFetchMode($this->defaultFetchMode); + + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function query() + { + $this->connect(); + + $stmt = call_user_func_array(array($this->_conn, 'query'), func_get_args()); + $stmt = new Statement($stmt, $this); + $stmt->setFetchMode($this->defaultFetchMode); + + return $stmt; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Portability/Statement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Portability/Statement.php new file mode 100644 index 00000000..352fda9d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Portability/Statement.php @@ -0,0 +1,240 @@ +. + */ + +namespace Doctrine\DBAL\Portability; + +use PDO; + +/** + * Portability wrapper for a Statement. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class Statement implements \IteratorAggregate, \Doctrine\DBAL\Driver\Statement +{ + /** + * @var integer + */ + private $portability; + + /** + * @var \Doctrine\DBAL\Driver\Statement + */ + private $stmt; + + /** + * @var integer + */ + private $case; + + /** + * @var integer + */ + private $defaultFetchMode = PDO::FETCH_BOTH; + + /** + * Wraps Statement and applies portability measures. + * + * @param \Doctrine\DBAL\Driver\Statement $stmt + * @param \Doctrine\DBAL\Portability\Connection $conn + */ + public function __construct($stmt, Connection $conn) + { + $this->stmt = $stmt; + $this->portability = $conn->getPortability(); + $this->case = $conn->getFetchCase(); + } + + /** + * {@inheritdoc} + */ + public function bindParam($column, &$variable, $type = null, $length = null) + { + return $this->stmt->bindParam($column, $variable, $type, $length); + } + /** + * {@inheritdoc} + */ + + public function bindValue($param, $value, $type = null) + { + return $this->stmt->bindValue($param, $value, $type); + } + + /** + * {@inheritdoc} + */ + public function closeCursor() + { + return $this->stmt->closeCursor(); + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return $this->stmt->columnCount(); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return $this->stmt->errorCode(); + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return $this->stmt->errorInfo(); + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + return $this->stmt->execute($params); + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg1 = null, $arg2 = null) + { + $this->defaultFetchMode = $fetchMode; + + return $this->stmt->setFetchMode($fetchMode, $arg1, $arg2); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $data = $this->fetchAll(); + + return new \ArrayIterator($data); + } + + /** + * {@inheritdoc} + */ + public function fetch($fetchMode = null) + { + $fetchMode = $fetchMode ?: $this->defaultFetchMode; + + $row = $this->stmt->fetch($fetchMode); + + $row = $this->fixRow($row, + $this->portability & (Connection::PORTABILITY_EMPTY_TO_NULL|Connection::PORTABILITY_RTRIM), + !is_null($this->case) && ($fetchMode == PDO::FETCH_ASSOC || $fetchMode == PDO::FETCH_BOTH) && ($this->portability & Connection::PORTABILITY_FIX_CASE) + ); + + return $row; + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null, $columnIndex = 0) + { + $fetchMode = $fetchMode ?: $this->defaultFetchMode; + + if ($columnIndex != 0) { + $rows = $this->stmt->fetchAll($fetchMode, $columnIndex); + } else { + $rows = $this->stmt->fetchAll($fetchMode); + } + + $iterateRow = $this->portability & (Connection::PORTABILITY_EMPTY_TO_NULL|Connection::PORTABILITY_RTRIM); + $fixCase = !is_null($this->case) && ($fetchMode == PDO::FETCH_ASSOC || $fetchMode == PDO::FETCH_BOTH) && ($this->portability & Connection::PORTABILITY_FIX_CASE); + if ( ! $iterateRow && !$fixCase) { + return $rows; + } + + foreach ($rows as $num => $row) { + $rows[$num] = $this->fixRow($row, $iterateRow, $fixCase); + } + + return $rows; + } + + /** + * @param mixed $row + * @param integer $iterateRow + * @param boolean $fixCase + * + * @return array + */ + protected function fixRow($row, $iterateRow, $fixCase) + { + if ( ! $row) { + return $row; + } + + if ($fixCase) { + $row = array_change_key_case($row, $this->case); + } + + if ($iterateRow) { + foreach ($row as $k => $v) { + if (($this->portability & Connection::PORTABILITY_EMPTY_TO_NULL) && $v === '') { + $row[$k] = null; + } elseif (($this->portability & Connection::PORTABILITY_RTRIM) && is_string($v)) { + $row[$k] = rtrim($v); + } + } + } + + return $row; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + $value = $this->stmt->fetchColumn($columnIndex); + + if ($this->portability & (Connection::PORTABILITY_EMPTY_TO_NULL|Connection::PORTABILITY_RTRIM)) { + if (($this->portability & Connection::PORTABILITY_EMPTY_TO_NULL) && $value === '') { + $value = null; + } elseif (($this->portability & Connection::PORTABILITY_RTRIM) && is_string($value)) { + $value = rtrim($value); + } + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + return $this->stmt->rowCount(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/CompositeExpression.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/CompositeExpression.php new file mode 100644 index 00000000..937726f0 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/CompositeExpression.php @@ -0,0 +1,134 @@ +. + */ + +namespace Doctrine\DBAL\Query\Expression; + +/** + * Composite expression is responsible to build a group of similar expression. + * + * @link www.doctrine-project.org + * @since 2.1 + * @author Guilherme Blanco + * @author Benjamin Eberlei + */ +class CompositeExpression implements \Countable +{ + /** + * Constant that represents an AND composite expression. + */ + const TYPE_AND = 'AND'; + + /** + * Constant that represents an OR composite expression. + */ + const TYPE_OR = 'OR'; + + /** + * The instance type of composite expression. + * + * @var string + */ + private $type; + + /** + * Each expression part of the composite expression. + * + * @var array + */ + private $parts = array(); + + /** + * Constructor. + * + * @param string $type Instance type of composite expression. + * @param array $parts Composition of expressions to be joined on composite expression. + */ + public function __construct($type, array $parts = array()) + { + $this->type = $type; + + $this->addMultiple($parts); + } + + /** + * Adds multiple parts to composite expression. + * + * @param array $parts + * + * @return \Doctrine\DBAL\Query\Expression\CompositeExpression + */ + public function addMultiple(array $parts = array()) + { + foreach ((array) $parts as $part) { + $this->add($part); + } + + return $this; + } + + /** + * Adds an expression to composite expression. + * + * @param mixed $part + * + * @return \Doctrine\DBAL\Query\Expression\CompositeExpression + */ + public function add($part) + { + if ( ! empty($part) || ($part instanceof self && $part->count() > 0)) { + $this->parts[] = $part; + } + + return $this; + } + + /** + * Retrieves the amount of expressions on composite expression. + * + * @return integer + */ + public function count() + { + return count($this->parts); + } + + /** + * Retrieves the string representation of this composite expression. + * + * @return string + */ + public function __toString() + { + if (count($this->parts) === 1) { + return (string) $this->parts[0]; + } + + return '(' . implode(') ' . $this->type . ' (', $this->parts) . ')'; + } + + /** + * Returns the type of this composite expression (AND/OR). + * + * @return string + */ + public function getType() + { + return $this->type; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/ExpressionBuilder.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/ExpressionBuilder.php new file mode 100644 index 00000000..2736fcf2 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/ExpressionBuilder.php @@ -0,0 +1,313 @@ +. + */ + +namespace Doctrine\DBAL\Query\Expression; + +use Doctrine\DBAL\Connection; + +/** + * ExpressionBuilder class is responsible to dynamically create SQL query parts. + * + * @link www.doctrine-project.org + * @since 2.1 + * @author Guilherme Blanco + * @author Benjamin Eberlei + */ +class ExpressionBuilder +{ + const EQ = '='; + const NEQ = '<>'; + const LT = '<'; + const LTE = '<='; + const GT = '>'; + const GTE = '>='; + + /** + * The DBAL Connection. + * + * @var \Doctrine\DBAL\Connection + */ + private $connection; + + /** + * Initializes a new ExpressionBuilder. + * + * @param \Doctrine\DBAL\Connection $connection The DBAL Connection. + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * Creates a conjunction of the given boolean expressions. + * + * Example: + * + * [php] + * // (u.type = ?) AND (u.role = ?) + * $expr->andX('u.type = ?', 'u.role = ?')); + * + * @param mixed $x Optional clause. Defaults = null, but requires + * at least one defined when converting to string. + * + * @return \Doctrine\DBAL\Query\Expression\CompositeExpression + */ + public function andX($x = null) + { + return new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args()); + } + + /** + * Creates a disjunction of the given boolean expressions. + * + * Example: + * + * [php] + * // (u.type = ?) OR (u.role = ?) + * $qb->where($qb->expr()->orX('u.type = ?', 'u.role = ?')); + * + * @param mixed $x Optional clause. Defaults = null, but requires + * at least one defined when converting to string. + * + * @return \Doctrine\DBAL\Query\Expression\CompositeExpression + */ + public function orX($x = null) + { + return new CompositeExpression(CompositeExpression::TYPE_OR, func_get_args()); + } + + /** + * Creates a comparison expression. + * + * @param mixed $x The left expression. + * @param string $operator One of the ExpressionBuilder::* constants. + * @param mixed $y The right expression. + * + * @return string + */ + public function comparison($x, $operator, $y) + { + return $x . ' ' . $operator . ' ' . $y; + } + + /** + * Creates an equality comparison expression with the given arguments. + * + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a = . Example: + * + * [php] + * // u.id = ? + * $expr->eq('u.id', '?'); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function eq($x, $y) + { + return $this->comparison($x, self::EQ, $y); + } + + /** + * Creates a non equality comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a <> . Example: + * + * [php] + * // u.id <> 1 + * $q->where($q->expr()->neq('u.id', '1')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function neq($x, $y) + { + return $this->comparison($x, self::NEQ, $y); + } + + /** + * Creates a lower-than comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a < . Example: + * + * [php] + * // u.id < ? + * $q->where($q->expr()->lt('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function lt($x, $y) + { + return $this->comparison($x, self::LT, $y); + } + + /** + * Creates a lower-than-equal comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a <= . Example: + * + * [php] + * // u.id <= ? + * $q->where($q->expr()->lte('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function lte($x, $y) + { + return $this->comparison($x, self::LTE, $y); + } + + /** + * Creates a greater-than comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a > . Example: + * + * [php] + * // u.id > ? + * $q->where($q->expr()->gt('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function gt($x, $y) + { + return $this->comparison($x, self::GT, $y); + } + + /** + * Creates a greater-than-equal comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a >= . Example: + * + * [php] + * // u.id >= ? + * $q->where($q->expr()->gte('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function gte($x, $y) + { + return $this->comparison($x, self::GTE, $y); + } + + /** + * Creates an IS NULL expression with the given arguments. + * + * @param string $x The field in string format to be restricted by IS NULL. + * + * @return string + */ + public function isNull($x) + { + return $x . ' IS NULL'; + } + + /** + * Creates an IS NOT NULL expression with the given arguments. + * + * @param string $x The field in string format to be restricted by IS NOT NULL. + * + * @return string + */ + public function isNotNull($x) + { + return $x . ' IS NOT NULL'; + } + + /** + * Creates a LIKE() comparison expression with the given arguments. + * + * @param string $x Field in string format to be inspected by LIKE() comparison. + * @param mixed $y Argument to be used in LIKE() comparison. + * + * @return string + */ + public function like($x, $y) + { + return $this->comparison($x, 'LIKE', $y); + } + + /** + * Creates a NOT LIKE() comparison expression with the given arguments. + * + * @param string $x Field in string format to be inspected by NOT LIKE() comparison. + * @param mixed $y Argument to be used in NOT LIKE() comparison. + * + * @return string + */ + public function notLike($x, $y) + { + return $this->comparison($x, 'NOT LIKE', $y); + } + + /** + * Creates a IN () comparison expression with the given arguments. + * + * @param string $x The field in string format to be inspected by IN() comparison. + * @param string|array $y The placeholder or the array of values to be used by IN() comparison. + * + * @return string + */ + public function in($x, $y) + { + return $this->comparison($x, 'IN', '('.implode(', ', (array) $y).')'); + } + + /** + * Creates a NOT IN () comparison expression with the given arguments. + * + * @param string $x The field in string format to be inspected by NOT IN() comparison. + * @param string|array $y The placeholder or the array of values to be used by NOT IN() comparison. + * + * @return string + */ + public function notIn($x, $y) + { + return $this->comparison($x, 'NOT IN', '('.implode(', ', (array) $y).')'); + } + + /** + * Quotes a given input parameter. + * + * @param mixed $input The parameter to be quoted. + * @param string|null $type The type of the parameter. + * + * @return string + */ + public function literal($input, $type = null) + { + return $this->connection->quote($input, $type); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryBuilder.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryBuilder.php new file mode 100644 index 00000000..5c46f1aa --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryBuilder.php @@ -0,0 +1,1354 @@ +. + */ + +namespace Doctrine\DBAL\Query; + +use Doctrine\DBAL\Query\Expression\CompositeExpression; +use Doctrine\DBAL\Connection; + +/** + * QueryBuilder class is responsible to dynamically create SQL queries. + * + * Important: Verify that every feature you use will work with your database vendor. + * SQL Query Builder does not attempt to validate the generated SQL at all. + * + * The query builder does no validation whatsoever if certain features even work with the + * underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements + * even if some vendors such as MySQL support it. + * + * @link www.doctrine-project.org + * @since 2.1 + * @author Guilherme Blanco + * @author Benjamin Eberlei + */ +class QueryBuilder +{ + /* + * The query types. + */ + const SELECT = 0; + const DELETE = 1; + const UPDATE = 2; + const INSERT = 3; + + /* + * The builder states. + */ + const STATE_DIRTY = 0; + const STATE_CLEAN = 1; + + /** + * The DBAL Connection. + * + * @var \Doctrine\DBAL\Connection + */ + private $connection; + + /** + * @var array The array of SQL parts collected. + */ + private $sqlParts = array( + 'select' => array(), + 'from' => array(), + 'join' => array(), + 'set' => array(), + 'where' => null, + 'groupBy' => array(), + 'having' => null, + 'orderBy' => array(), + 'values' => array(), + ); + + /** + * The complete SQL string for this query. + * + * @var string + */ + private $sql; + + /** + * The query parameters. + * + * @var array + */ + private $params = array(); + + /** + * The parameter type map of this query. + * + * @var array + */ + private $paramTypes = array(); + + /** + * The type of query this is. Can be select, update or delete. + * + * @var integer + */ + private $type = self::SELECT; + + /** + * The state of the query object. Can be dirty or clean. + * + * @var integer + */ + private $state = self::STATE_CLEAN; + + /** + * The index of the first result to retrieve. + * + * @var integer + */ + private $firstResult = null; + + /** + * The maximum number of results to retrieve. + * + * @var integer + */ + private $maxResults = null; + + /** + * The counter of bound parameters used with {@see bindValue). + * + * @var integer + */ + private $boundCounter = 0; + + /** + * Initializes a new QueryBuilder. + * + * @param \Doctrine\DBAL\Connection $connection The DBAL Connection. + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * Gets an ExpressionBuilder used for object-oriented construction of query expressions. + * This producer method is intended for convenient inline usage. Example: + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where($qb->expr()->eq('u.id', 1)); + * + * + * For more complex expression construction, consider storing the expression + * builder object in a local variable. + * + * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder + */ + public function expr() + { + return $this->connection->getExpressionBuilder(); + } + + /** + * Gets the type of the currently built query. + * + * @return integer + */ + public function getType() + { + return $this->type; + } + + /** + * Gets the associated DBAL Connection for this query builder. + * + * @return \Doctrine\DBAL\Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Gets the state of this query builder instance. + * + * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN. + */ + public function getState() + { + return $this->state; + } + + /** + * Executes this query using the bound parameters and their types. + * + * Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate} + * for insert, update and delete statements. + * + * @return \Doctrine\DBAL\Driver\Statement|int + */ + public function execute() + { + if ($this->type == self::SELECT) { + return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes); + } else { + return $this->connection->executeUpdate($this->getSQL(), $this->params, $this->paramTypes); + } + } + + /** + * Gets the complete SQL string formed by the current specifications of this QueryBuilder. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * echo $qb->getSQL(); // SELECT u FROM User u + * + * + * @return string The SQL query string. + */ + public function getSQL() + { + if ($this->sql !== null && $this->state === self::STATE_CLEAN) { + return $this->sql; + } + + switch ($this->type) { + case self::INSERT: + $sql = $this->getSQLForInsert(); + break; + case self::DELETE: + $sql = $this->getSQLForDelete(); + break; + + case self::UPDATE: + $sql = $this->getSQLForUpdate(); + break; + + case self::SELECT: + default: + $sql = $this->getSQLForSelect(); + break; + } + + $this->state = self::STATE_CLEAN; + $this->sql = $sql; + + return $sql; + } + + /** + * Sets a query parameter for the query being constructed. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where('u.id = :user_id') + * ->setParameter(':user_id', 1); + * + * + * @param string|integer $key The parameter position or name. + * @param mixed $value The parameter value. + * @param string|null $type One of the PDO::PARAM_* constants. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function setParameter($key, $value, $type = null) + { + if ($type !== null) { + $this->paramTypes[$key] = $type; + } + + $this->params[$key] = $value; + + return $this; + } + + /** + * Sets a collection of query parameters for the query being constructed. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where('u.id = :user_id1 OR u.id = :user_id2') + * ->setParameters(array( + * ':user_id1' => 1, + * ':user_id2' => 2 + * )); + * + * + * @param array $params The query parameters to set. + * @param array $types The query parameters types to set. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function setParameters(array $params, array $types = array()) + { + $this->paramTypes = $types; + $this->params = $params; + + return $this; + } + + /** + * Gets all defined query parameters for the query being constructed indexed by parameter index or name. + * + * @return array The currently defined query parameters indexed by parameter index or name. + */ + public function getParameters() + { + return $this->params; + } + + /** + * Gets a (previously set) query parameter of the query being constructed. + * + * @param mixed $key The key (index or name) of the bound parameter. + * + * @return mixed The value of the bound parameter. + */ + public function getParameter($key) + { + return isset($this->params[$key]) ? $this->params[$key] : null; + } + + /** + * Gets all defined query parameter types for the query being constructed indexed by parameter index or name. + * + * @return array The currently defined query parameter types indexed by parameter index or name. + */ + public function getParameterTypes() + { + return $this->paramTypes; + } + + /** + * Gets a (previously set) query parameter type of the query being constructed. + * + * @param mixed $key The key (index or name) of the bound parameter type. + * + * @return mixed The value of the bound parameter type. + */ + public function getParameterType($key) + { + return isset($this->paramTypes[$key]) ? $this->paramTypes[$key] : null; + } + + /** + * Sets the position of the first result to retrieve (the "offset"). + * + * @param integer $firstResult The first result to return. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function setFirstResult($firstResult) + { + $this->state = self::STATE_DIRTY; + $this->firstResult = $firstResult; + + return $this; + } + + /** + * Gets the position of the first result the query object was set to retrieve (the "offset"). + * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder. + * + * @return integer The position of the first result. + */ + public function getFirstResult() + { + return $this->firstResult; + } + + /** + * Sets the maximum number of results to retrieve (the "limit"). + * + * @param integer $maxResults The maximum number of results to retrieve. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function setMaxResults($maxResults) + { + $this->state = self::STATE_DIRTY; + $this->maxResults = $maxResults; + + return $this; + } + + /** + * Gets the maximum number of results the query object was set to retrieve (the "limit"). + * Returns NULL if {@link setMaxResults} was not applied to this query builder. + * + * @return integer The maximum number of results. + */ + public function getMaxResults() + { + return $this->maxResults; + } + + /** + * Either appends to or replaces a single, generic query part. + * + * The available parts are: 'select', 'from', 'set', 'where', + * 'groupBy', 'having' and 'orderBy'. + * + * @param string $sqlPartName + * @param string $sqlPart + * @param boolean $append + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function add($sqlPartName, $sqlPart, $append = false) + { + $isArray = is_array($sqlPart); + $isMultiple = is_array($this->sqlParts[$sqlPartName]); + + if ($isMultiple && !$isArray) { + $sqlPart = array($sqlPart); + } + + $this->state = self::STATE_DIRTY; + + if ($append) { + if ($sqlPartName == "orderBy" || $sqlPartName == "groupBy" || $sqlPartName == "select" || $sqlPartName == "set") { + foreach ($sqlPart as $part) { + $this->sqlParts[$sqlPartName][] = $part; + } + } elseif ($isArray && is_array($sqlPart[key($sqlPart)])) { + $key = key($sqlPart); + $this->sqlParts[$sqlPartName][$key][] = $sqlPart[$key]; + } elseif ($isMultiple) { + $this->sqlParts[$sqlPartName][] = $sqlPart; + } else { + $this->sqlParts[$sqlPartName] = $sqlPart; + } + + return $this; + } + + $this->sqlParts[$sqlPartName] = $sqlPart; + + return $this; + } + + /** + * Specifies an item that is to be returned in the query result. + * Replaces any previously specified selections, if any. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.id', 'p.id') + * ->from('users', 'u') + * ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id'); + * + * + * @param mixed $select The selection expressions. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function select($select = null) + { + $this->type = self::SELECT; + + if (empty($select)) { + return $this; + } + + $selects = is_array($select) ? $select : func_get_args(); + + return $this->add('select', $selects, false); + } + + /** + * Adds an item that is to be returned in the query result. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.id') + * ->addSelect('p.id') + * ->from('users', 'u') + * ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id'); + * + * + * @param mixed $select The selection expression. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function addSelect($select = null) + { + $this->type = self::SELECT; + + if (empty($select)) { + return $this; + } + + $selects = is_array($select) ? $select : func_get_args(); + + return $this->add('select', $selects, true); + } + + /** + * Turns the query being built into a bulk delete query that ranges over + * a certain table. + * + * + * $qb = $conn->createQueryBuilder() + * ->delete('users', 'u') + * ->where('u.id = :user_id'); + * ->setParameter(':user_id', 1); + * + * + * @param string $delete The table whose rows are subject to the deletion. + * @param string $alias The table alias used in the constructed query. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function delete($delete = null, $alias = null) + { + $this->type = self::DELETE; + + if ( ! $delete) { + return $this; + } + + return $this->add('from', array( + 'table' => $delete, + 'alias' => $alias + )); + } + + /** + * Turns the query being built into a bulk update query that ranges over + * a certain table + * + * + * $qb = $conn->createQueryBuilder() + * ->update('users', 'u') + * ->set('u.password', md5('password')) + * ->where('u.id = ?'); + * + * + * @param string $update The table whose rows are subject to the update. + * @param string $alias The table alias used in the constructed query. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function update($update = null, $alias = null) + { + $this->type = self::UPDATE; + + if ( ! $update) { + return $this; + } + + return $this->add('from', array( + 'table' => $update, + 'alias' => $alias + )); + } + + /** + * Turns the query being built into an insert query that inserts into + * a certain table + * + * + * $qb = $conn->createQueryBuilder() + * ->insert('users') + * ->values( + * array( + * 'name' => '?', + * 'password' => '?' + * ) + * ); + * + * + * @param string $insert The table into which the rows should be inserted. + * + * @return QueryBuilder This QueryBuilder instance. + */ + public function insert($insert = null) + { + $this->type = self::INSERT; + + if ( ! $insert) { + return $this; + } + + return $this->add('from', array( + 'table' => $insert + )); + } + + /** + * Creates and adds a query root corresponding to the table identified by the + * given alias, forming a cartesian product with any existing query roots. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.id') + * ->from('users', 'u') + * + * + * @param string $from The table. + * @param string|null $alias The alias of the table. + * + * @return QueryBuilder This QueryBuilder instance. + */ + public function from($from, $alias = null) + { + return $this->add('from', array( + 'table' => $from, + 'alias' => $alias + ), true); + } + + /** + * Creates and adds a join to the query. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function join($fromAlias, $join, $alias, $condition = null) + { + return $this->innerJoin($fromAlias, $join, $alias, $condition); + } + + /** + * Creates and adds a join to the query. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function innerJoin($fromAlias, $join, $alias, $condition = null) + { + return $this->add('join', array( + $fromAlias => array( + 'joinType' => 'inner', + 'joinTable' => $join, + 'joinAlias' => $alias, + 'joinCondition' => $condition + ) + ), true); + } + + /** + * Creates and adds a left join to the query. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function leftJoin($fromAlias, $join, $alias, $condition = null) + { + return $this->add('join', array( + $fromAlias => array( + 'joinType' => 'left', + 'joinTable' => $join, + 'joinAlias' => $alias, + 'joinCondition' => $condition + ) + ), true); + } + + /** + * Creates and adds a right join to the query. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function rightJoin($fromAlias, $join, $alias, $condition = null) + { + return $this->add('join', array( + $fromAlias => array( + 'joinType' => 'right', + 'joinTable' => $join, + 'joinAlias' => $alias, + 'joinCondition' => $condition + ) + ), true); + } + + /** + * Sets a new value for a column in a bulk update query. + * + * + * $qb = $conn->createQueryBuilder() + * ->update('users', 'u') + * ->set('u.password', md5('password')) + * ->where('u.id = ?'); + * + * + * @param string $key The column to set. + * @param string $value The value, expression, placeholder, etc. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function set($key, $value) + { + return $this->add('set', $key .' = ' . $value, true); + } + + /** + * Specifies one or more restrictions to the query result. + * Replaces any previously specified restrictions, if any. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->where('u.id = ?'); + * + * // You can optionally programatically build and/or expressions + * $qb = $conn->createQueryBuilder(); + * + * $or = $qb->expr()->orx(); + * $or->add($qb->expr()->eq('u.id', 1)); + * $or->add($qb->expr()->eq('u.id', 2)); + * + * $qb->update('users', 'u') + * ->set('u.password', md5('password')) + * ->where($or); + * + * + * @param mixed $predicates The restriction predicates. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function where($predicates) + { + if ( ! (func_num_args() == 1 && $predicates instanceof CompositeExpression)) { + $predicates = new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args()); + } + + return $this->add('where', $predicates); + } + + /** + * Adds one or more restrictions to the query results, forming a logical + * conjunction with any previously specified restrictions. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where('u.username LIKE ?') + * ->andWhere('u.is_active = 1'); + * + * + * @param mixed $where The query restrictions. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + * + * @see where() + */ + public function andWhere($where) + { + $args = func_get_args(); + $where = $this->getQueryPart('where'); + + if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_AND) { + $where->addMultiple($args); + } else { + array_unshift($args, $where); + $where = new CompositeExpression(CompositeExpression::TYPE_AND, $args); + } + + return $this->add('where', $where, true); + } + + /** + * Adds one or more restrictions to the query results, forming a logical + * disjunction with any previously specified restrictions. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->where('u.id = 1') + * ->orWhere('u.id = 2'); + * + * + * @param mixed $where The WHERE statement. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + * + * @see where() + */ + public function orWhere($where) + { + $args = func_get_args(); + $where = $this->getQueryPart('where'); + + if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_OR) { + $where->addMultiple($args); + } else { + array_unshift($args, $where); + $where = new CompositeExpression(CompositeExpression::TYPE_OR, $args); + } + + return $this->add('where', $where, true); + } + + /** + * Specifies a grouping over the results of the query. + * Replaces any previously specified groupings, if any. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->groupBy('u.id'); + * + * + * @param mixed $groupBy The grouping expression. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function groupBy($groupBy) + { + if (empty($groupBy)) { + return $this; + } + + $groupBy = is_array($groupBy) ? $groupBy : func_get_args(); + + return $this->add('groupBy', $groupBy, false); + } + + + /** + * Adds a grouping expression to the query. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->groupBy('u.lastLogin'); + * ->addGroupBy('u.createdAt') + * + * + * @param mixed $groupBy The grouping expression. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function addGroupBy($groupBy) + { + if (empty($groupBy)) { + return $this; + } + + $groupBy = is_array($groupBy) ? $groupBy : func_get_args(); + + return $this->add('groupBy', $groupBy, true); + } + + /** + * Sets a value for a column in an insert query. + * + * + * $qb = $conn->createQueryBuilder() + * ->insert('users') + * ->values( + * array( + * 'name' => '?' + * ) + * ) + * ->setValue('password', '?'); + * + * + * @param string $column The column into which the value should be inserted. + * @param string $value The value that should be inserted into the column. + * + * @return QueryBuilder This QueryBuilder instance. + */ + public function setValue($column, $value) + { + $this->sqlParts['values'][$column] = $value; + + return $this; + } + + /** + * Specifies values for an insert query indexed by column names. + * Replaces any previous values, if any. + * + * + * $qb = $conn->createQueryBuilder() + * ->insert('users') + * ->values( + * array( + * 'name' => '?', + * 'password' => '?' + * ) + * ); + * + * + * @param array $values The values to specify for the insert query indexed by column names. + * + * @return QueryBuilder This QueryBuilder instance. + */ + public function values(array $values) + { + return $this->add('values', $values); + } + + /** + * Specifies a restriction over the groups of the query. + * Replaces any previous having restrictions, if any. + * + * @param mixed $having The restriction over the groups. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function having($having) + { + if ( ! (func_num_args() == 1 && $having instanceof CompositeExpression)) { + $having = new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args()); + } + + return $this->add('having', $having); + } + + /** + * Adds a restriction over the groups of the query, forming a logical + * conjunction with any existing having restrictions. + * + * @param mixed $having The restriction to append. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function andHaving($having) + { + $args = func_get_args(); + $having = $this->getQueryPart('having'); + + if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_AND) { + $having->addMultiple($args); + } else { + array_unshift($args, $having); + $having = new CompositeExpression(CompositeExpression::TYPE_AND, $args); + } + + return $this->add('having', $having); + } + + /** + * Adds a restriction over the groups of the query, forming a logical + * disjunction with any existing having restrictions. + * + * @param mixed $having The restriction to add. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function orHaving($having) + { + $args = func_get_args(); + $having = $this->getQueryPart('having'); + + if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_OR) { + $having->addMultiple($args); + } else { + array_unshift($args, $having); + $having = new CompositeExpression(CompositeExpression::TYPE_OR, $args); + } + + return $this->add('having', $having); + } + + /** + * Specifies an ordering for the query results. + * Replaces any previously specified orderings, if any. + * + * @param string $sort The ordering expression. + * @param string $order The ordering direction. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function orderBy($sort, $order = null) + { + return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), false); + } + + /** + * Adds an ordering to the query results. + * + * @param string $sort The ordering expression. + * @param string $order The ordering direction. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function addOrderBy($sort, $order = null) + { + return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), true); + } + + /** + * Gets a query part by its name. + * + * @param string $queryPartName + * + * @return mixed + */ + public function getQueryPart($queryPartName) + { + return $this->sqlParts[$queryPartName]; + } + + /** + * Gets all query parts. + * + * @return array + */ + public function getQueryParts() + { + return $this->sqlParts; + } + + /** + * Resets SQL parts. + * + * @param array|null $queryPartNames + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function resetQueryParts($queryPartNames = null) + { + if (is_null($queryPartNames)) { + $queryPartNames = array_keys($this->sqlParts); + } + + foreach ($queryPartNames as $queryPartName) { + $this->resetQueryPart($queryPartName); + } + + return $this; + } + + /** + * Resets a single SQL part. + * + * @param string $queryPartName + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function resetQueryPart($queryPartName) + { + $this->sqlParts[$queryPartName] = is_array($this->sqlParts[$queryPartName]) + ? array() : null; + + $this->state = self::STATE_DIRTY; + + return $this; + } + + /** + * @return string + * + * @throws \Doctrine\DBAL\Query\QueryException + */ + private function getSQLForSelect() + { + $query = 'SELECT ' . implode(', ', $this->sqlParts['select']); + + $query .= ($this->sqlParts['from'] ? ' FROM ' . implode(', ', $this->getFromClauses()) : '') + . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '') + . ($this->sqlParts['groupBy'] ? ' GROUP BY ' . implode(', ', $this->sqlParts['groupBy']) : '') + . ($this->sqlParts['having'] !== null ? ' HAVING ' . ((string) $this->sqlParts['having']) : '') + . ($this->sqlParts['orderBy'] ? ' ORDER BY ' . implode(', ', $this->sqlParts['orderBy']) : ''); + + if ($this->isLimitQuery()) { + return $this->connection->getDatabasePlatform()->modifyLimitQuery( + $query, + $this->maxResults, + $this->firstResult + ); + } + + return $query; + } + + /** + * @return string[] + */ + private function getFromClauses() + { + $fromClauses = array(); + $knownAliases = array(); + + // Loop through all FROM clauses + foreach ($this->sqlParts['from'] as $from) { + if ($from['alias'] === null) { + $tableSql = $from['table']; + $tableReference = $from['table']; + } else { + $tableSql = $from['table'] . ' ' . $from['alias']; + $tableReference = $from['alias']; + } + + $knownAliases[$tableReference] = true; + + $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases); + } + + $this->verifyAllAliasesAreKnown($knownAliases); + + return $fromClauses; + } + + /** + * @param array $knownAliases + * + * @throws QueryException + */ + private function verifyAllAliasesAreKnown(array $knownAliases) + { + foreach ($this->sqlParts['join'] as $fromAlias => $joins) { + if ( ! isset($knownAliases[$fromAlias])) { + throw QueryException::unknownAlias($fromAlias, array_keys($knownAliases)); + } + } + } + + /** + * @return bool + */ + private function isLimitQuery() + { + return $this->maxResults !== null || $this->firstResult !== null; + } + + /** + * Converts this instance into an INSERT string in SQL. + * + * @return string + */ + private function getSQLForInsert() + { + return 'INSERT INTO ' . $this->sqlParts['from']['table'] . + ' (' . implode(', ', array_keys($this->sqlParts['values'])) . ')' . + ' VALUES(' . implode(', ', $this->sqlParts['values']) . ')'; + } + + /** + * Converts this instance into an UPDATE string in SQL. + * + * @return string + */ + private function getSQLForUpdate() + { + $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : ''); + $query = 'UPDATE ' . $table + . ' SET ' . implode(", ", $this->sqlParts['set']) + . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : ''); + + return $query; + } + + /** + * Converts this instance into a DELETE string in SQL. + * + * @return string + */ + private function getSQLForDelete() + { + $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : ''); + $query = 'DELETE FROM ' . $table . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : ''); + + return $query; + } + + /** + * Gets a string representation of this QueryBuilder which corresponds to + * the final SQL query being constructed. + * + * @return string The string representation of this QueryBuilder. + */ + public function __toString() + { + return $this->getSQL(); + } + + /** + * Creates a new named parameter and bind the value $value to it. + * + * This method provides a shortcut for PDOStatement::bindValue + * when using prepared statements. + * + * The parameter $value specifies the value that you want to bind. If + * $placeholder is not provided bindValue() will automatically create a + * placeholder for you. An automatic placeholder will be of the name + * ':dcValue1', ':dcValue2' etc. + * + * For more information see {@link http://php.net/pdostatement-bindparam} + * + * Example: + * + * $value = 2; + * $q->eq( 'id', $q->bindValue( $value ) ); + * $stmt = $q->executeQuery(); // executed with 'id = 2' + * + * + * @license New BSD License + * @link http://www.zetacomponents.org + * + * @param mixed $value + * @param mixed $type + * @param string $placeHolder The name to bind with. The string must start with a colon ':'. + * + * @return string the placeholder name used. + */ + public function createNamedParameter($value, $type = \PDO::PARAM_STR, $placeHolder = null) + { + if ($placeHolder === null) { + $this->boundCounter++; + $placeHolder = ":dcValue" . $this->boundCounter; + } + $this->setParameter(substr($placeHolder, 1), $value, $type); + + return $placeHolder; + } + + /** + * Creates a new positional parameter and bind the given value to it. + * + * Attention: If you are using positional parameters with the query builder you have + * to be very careful to bind all parameters in the order they appear in the SQL + * statement , otherwise they get bound in the wrong order which can lead to serious + * bugs in your code. + * + * Example: + * + * $qb = $conn->createQueryBuilder(); + * $qb->select('u.*') + * ->from('users', 'u') + * ->where('u.username = ' . $qb->createPositionalParameter('Foo', PDO::PARAM_STR)) + * ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', PDO::PARAM_STR)) + * + * + * @param mixed $value + * @param integer $type + * + * @return string + */ + public function createPositionalParameter($value, $type = \PDO::PARAM_STR) + { + $this->boundCounter++; + $this->setParameter($this->boundCounter, $value, $type); + + return "?"; + } + + /** + * @param string $fromAlias + * @param array $knownAliases + * + * @return string + */ + private function getSQLForJoins($fromAlias, array &$knownAliases) + { + $sql = ''; + + if (isset($this->sqlParts['join'][$fromAlias])) { + foreach ($this->sqlParts['join'][$fromAlias] as $join) { + if (array_key_exists($join['joinAlias'], $knownAliases)) { + throw QueryException::nonUniqueAlias($join['joinAlias'], array_keys($knownAliases)); + } + $sql .= ' ' . strtoupper($join['joinType']) + . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias'] + . ' ON ' . ((string) $join['joinCondition']); + $knownAliases[$join['joinAlias']] = true; + } + + foreach ($this->sqlParts['join'][$fromAlias] as $join) { + $sql .= $this->getSQLForJoins($join['joinAlias'], $knownAliases); + } + } + + return $sql; + } + + /** + * Deep clone of all expression objects in the SQL parts. + * + * @return void + */ + public function __clone() + { + foreach ($this->sqlParts as $part => $elements) { + if (is_array($this->sqlParts[$part])) { + foreach ($this->sqlParts[$part] as $idx => $element) { + if (is_object($element)) { + $this->sqlParts[$part][$idx] = clone $element; + } + } + } elseif (is_object($elements)) { + $this->sqlParts[$part] = clone $elements; + } + } + + foreach ($this->params as $name => $param) { + if (is_object($param)) { + $this->params[$name] = clone $param; + } + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryException.php new file mode 100644 index 00000000..92c9336d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryException.php @@ -0,0 +1,54 @@ +. + */ + +namespace Doctrine\DBAL\Query; + +use Doctrine\DBAL\DBALException; + +/** + * @since 2.1.4 + */ +class QueryException extends DBALException +{ + /** + * @param string $alias + * @param array $registeredAliases + * + * @return \Doctrine\DBAL\Query\QueryException + */ + static public function unknownAlias($alias, $registeredAliases) + { + return new self("The given alias '" . $alias . "' is not part of " . + "any FROM or JOIN clause table. The currently registered " . + "aliases are: " . implode(", ", $registeredAliases) . "."); + } + + /** + * @param string $alias + * @param array $registeredAliases + * + * @return \Doctrine\DBAL\Query\QueryException + */ + static public function nonUniqueAlias($alias, $registeredAliases) + { + return new self("The given alias '" . $alias . "' is not unique " . + "in FROM and JOIN clause table. The currently registered " . + "aliases are: " . implode(", ", $registeredAliases) . "."); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/README.markdown b/vendor/doctrine/dbal/lib/Doctrine/DBAL/README.markdown new file mode 100644 index 00000000..e69de29b diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtils.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtils.php new file mode 100644 index 00000000..6c324131 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtils.php @@ -0,0 +1,243 @@ +. + */ + +namespace Doctrine\DBAL; + +/** + * Utility class that parses sql statements with regard to types and parameters. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class SQLParserUtils +{ + const POSITIONAL_TOKEN = '\?'; + const NAMED_TOKEN = '(? integer pair (indexed from zero) for a positional statement + * and a string => int[] pair for a named statement. + * + * @param string $statement + * @param boolean $isPositional + * + * @return array + */ + static public function getPlaceholderPositions($statement, $isPositional = true) + { + $match = ($isPositional) ? '?' : ':'; + if (strpos($statement, $match) === false) { + return array(); + } + + $token = ($isPositional) ? self::POSITIONAL_TOKEN : self::NAMED_TOKEN; + $paramMap = array(); + + foreach (self::getUnquotedStatementFragments($statement) as $fragment) { + preg_match_all("/$token/", $fragment[0], $matches, PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $placeholder) { + if ($isPositional) { + $paramMap[] = $placeholder[1] + $fragment[1]; + } else { + $pos = $placeholder[1] + $fragment[1]; + $paramMap[$pos] = substr($placeholder[0], 1, strlen($placeholder[0])); + } + } + } + + return $paramMap; + } + + /** + * For a positional query this method can rewrite the sql statement with regard to array parameters. + * + * @param string $query The SQL query to execute. + * @param array $params The parameters to bind to the query. + * @param array $types The types the previous parameters are in. + * + * @return array + * + * @throws SQLParserUtilsException + */ + static public function expandListParameters($query, $params, $types) + { + $isPositional = is_int(key($params)); + $arrayPositions = array(); + $bindIndex = -1; + + if ($isPositional) { + ksort($params); + ksort($types); + } + + foreach ($types as $name => $type) { + ++$bindIndex; + + if ($type !== Connection::PARAM_INT_ARRAY && $type !== Connection::PARAM_STR_ARRAY) { + continue; + } + + if ($isPositional) { + $name = $bindIndex; + } + + $arrayPositions[$name] = false; + } + + if (( ! $arrayPositions && $isPositional)) { + return array($query, $params, $types); + } + + $paramPos = self::getPlaceholderPositions($query, $isPositional); + + if ($isPositional) { + $paramOffset = 0; + $queryOffset = 0; + $params = array_values($params); + $types = array_values($types); + + foreach ($paramPos as $needle => $needlePos) { + if ( ! isset($arrayPositions[$needle])) { + continue; + } + + $needle += $paramOffset; + $needlePos += $queryOffset; + $count = count($params[$needle]); + + $params = array_merge( + array_slice($params, 0, $needle), + $params[$needle], + array_slice($params, $needle + 1) + ); + + $types = array_merge( + array_slice($types, 0, $needle), + $count ? + array_fill(0, $count, $types[$needle] - Connection::ARRAY_PARAM_OFFSET) : // array needles are at PDO::PARAM_* + 100 + array(), + array_slice($types, $needle + 1) + ); + + $expandStr = $count ? implode(", ", array_fill(0, $count, "?")) : 'NULL'; + $query = substr($query, 0, $needlePos) . $expandStr . substr($query, $needlePos + 1); + + $paramOffset += ($count - 1); // Grows larger by number of parameters minus the replaced needle. + $queryOffset += (strlen($expandStr) - 1); + } + + return array($query, $params, $types); + } + + $queryOffset = 0; + $typesOrd = array(); + $paramsOrd = array(); + + foreach ($paramPos as $pos => $paramName) { + $paramLen = strlen($paramName) + 1; + $value = static::extractParam($paramName, $params, true); + + if ( ! isset($arrayPositions[$paramName]) && ! isset($arrayPositions[':' . $paramName])) { + $pos += $queryOffset; + $queryOffset -= ($paramLen - 1); + $paramsOrd[] = $value; + $typesOrd[] = static::extractParam($paramName, $types, false, \PDO::PARAM_STR); + $query = substr($query, 0, $pos) . '?' . substr($query, ($pos + $paramLen)); + + continue; + } + + $count = count($value); + $expandStr = $count > 0 ? implode(', ', array_fill(0, $count, '?')) : 'NULL'; + + foreach ($value as $val) { + $paramsOrd[] = $val; + $typesOrd[] = static::extractParam($paramName, $types, false) - Connection::ARRAY_PARAM_OFFSET; + } + + $pos += $queryOffset; + $queryOffset += (strlen($expandStr) - $paramLen); + $query = substr($query, 0, $pos) . $expandStr . substr($query, ($pos + $paramLen)); + } + + return array($query, $paramsOrd, $typesOrd); + } + + /** + * Slice the SQL statement around pairs of quotes and + * return string fragments of SQL outside of quoted literals. + * Each fragment is captured as a 2-element array: + * + * 0 => matched fragment string, + * 1 => offset of fragment in $statement + * + * @param string $statement + * @return array + */ + static private function getUnquotedStatementFragments($statement) + { + $literal = self::ESCAPED_SINGLE_QUOTED_TEXT . '|' . + self::ESCAPED_DOUBLE_QUOTED_TEXT . '|' . + self::ESCAPED_BACKTICK_QUOTED_TEXT . '|' . + self::ESCAPED_BRACKET_QUOTED_TEXT; + preg_match_all("/([^'\"`\[]+)(?:$literal)?/s", $statement, $fragments, PREG_OFFSET_CAPTURE); + + return $fragments[1]; + } + + /** + * @param string $paramName The name of the parameter (without a colon in front) + * @param array $paramsOrTypes A hash of parameters or types + * @param bool $isParam + * @param mixed $defaultValue An optional default value. If omitted, an exception is thrown + * + * @throws SQLParserUtilsException + * @return mixed + */ + static private function extractParam($paramName, $paramsOrTypes, $isParam, $defaultValue = null) + { + if (array_key_exists($paramName, $paramsOrTypes)) { + return $paramsOrTypes[$paramName]; + } + + // Hash keys can be prefixed with a colon for compatibility + if (array_key_exists(':' . $paramName, $paramsOrTypes)) { + return $paramsOrTypes[':' . $paramName]; + } + + if (null !== $defaultValue) { + return $defaultValue; + } + + if ($isParam) { + throw SQLParserUtilsException::missingParam($paramName); + } + + throw SQLParserUtilsException::missingType($paramName); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtilsException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtilsException.php new file mode 100644 index 00000000..25c7209c --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtilsException.php @@ -0,0 +1,53 @@ +. + */ + +namespace Doctrine\DBAL; + +/** + * Doctrine\DBAL\ConnectionException + * + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link www.doctrine-project.org + * @since 2.4 + * @author Lars Strojny + */ +class SQLParserUtilsException extends DBALException +{ + /** + * @param string $paramName + * + * @return \Doctrine\DBAL\SQLParserUtilsException + */ + public static function missingParam($paramName) + { + return new self(sprintf('Value for :%1$s not found in params array. Params array key should be "%1$s"', $paramName)); + } + + /** + * @param string $typeName + * + * @return \Doctrine\DBAL\SQLParserUtilsException + */ + public static function missingType($typeName) + { + return new self(sprintf('Value for :%1$s not found in types array. Types array key should be "%1$s"', $typeName)); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractAsset.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractAsset.php new file mode 100644 index 00000000..051dce92 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractAsset.php @@ -0,0 +1,227 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * The abstract asset allows to reset the name of all assets without publishing this to the public userland. + * + * This encapsulation hack is necessary to keep a consistent state of the database schema. Say we have a list of tables + * array($tableName => Table($tableName)); if you want to rename the table, you have to make sure + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +abstract class AbstractAsset +{ + /** + * @var string + */ + protected $_name; + + /** + * Namespace of the asset. If none isset the default namespace is assumed. + * + * @var string|null + */ + protected $_namespace = null; + + /** + * @var boolean + */ + protected $_quoted = false; + + /** + * Sets the name of this asset. + * + * @param string $name + * + * @return void + */ + protected function _setName($name) + { + if ($this->isIdentifierQuoted($name)) { + $this->_quoted = true; + $name = $this->trimQuotes($name); + } + if (strpos($name, ".") !== false) { + $parts = explode(".", $name); + $this->_namespace = $parts[0]; + $name = $parts[1]; + } + $this->_name = $name; + } + + /** + * Is this asset in the default namespace? + * + * @param string $defaultNamespaceName + * + * @return boolean + */ + public function isInDefaultNamespace($defaultNamespaceName) + { + return $this->_namespace == $defaultNamespaceName || $this->_namespace === null; + } + + /** + * Gets the namespace name of this asset. + * + * If NULL is returned this means the default namespace is used. + * + * @return string|null + */ + public function getNamespaceName() + { + return $this->_namespace; + } + + /** + * The shortest name is stripped of the default namespace. All other + * namespaced elements are returned as full-qualified names. + * + * @param string $defaultNamespaceName + * + * @return string + */ + public function getShortestName($defaultNamespaceName) + { + $shortestName = $this->getName(); + if ($this->_namespace == $defaultNamespaceName) { + $shortestName = $this->_name; + } + + return strtolower($shortestName); + } + + /** + * The normalized name is full-qualified and lowerspaced. Lowerspacing is + * actually wrong, but we have to do it to keep our sanity. If you are + * using database objects that only differentiate in the casing (FOO vs + * Foo) then you will NOT be able to use Doctrine Schema abstraction. + * + * Every non-namespaced element is prefixed with the default namespace + * name which is passed as argument to this method. + * + * @param string $defaultNamespaceName + * + * @return string + */ + public function getFullQualifiedName($defaultNamespaceName) + { + $name = $this->getName(); + if ( ! $this->_namespace) { + $name = $defaultNamespaceName . "." . $name; + } + + return strtolower($name); + } + + /** + * Checks if this asset's name is quoted. + * + * @return boolean + */ + public function isQuoted() + { + return $this->_quoted; + } + + /** + * Checks if this identifier is quoted. + * + * @param string $identifier + * + * @return boolean + */ + protected function isIdentifierQuoted($identifier) + { + return (isset($identifier[0]) && ($identifier[0] == '`' || $identifier[0] == '"' || $identifier[0] == '[')); + } + + /** + * Trim quotes from the identifier. + * + * @param string $identifier + * + * @return string + */ + protected function trimQuotes($identifier) + { + return str_replace(array('`', '"', '[', ']'), '', $identifier); + } + + /** + * Returns the name of this schema asset. + * + * @return string + */ + public function getName() + { + if ($this->_namespace) { + return $this->_namespace . "." . $this->_name; + } + + return $this->_name; + } + + /** + * Gets the quoted representation of this asset but only if it was defined with one. Otherwise + * return the plain unquoted value as inserted. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return string + */ + public function getQuotedName(AbstractPlatform $platform) + { + $keywords = $platform->getReservedKeywordsList(); + $parts = explode(".", $this->getName()); + foreach ($parts as $k => $v) { + $parts[$k] = ($this->_quoted || $keywords->isKeyword($v)) ? $platform->quoteIdentifier($v) : $v; + } + + return implode(".", $parts); + } + + /** + * Generates an identifier from a list of column names obeying a certain string length. + * + * This is especially important for Oracle, since it does not allow identifiers larger than 30 chars, + * however building idents automatically for foreign keys, composite keys or such can easily create + * very long names. + * + * @param array $columnNames + * @param string $prefix + * @param integer $maxSize + * + * @return string + */ + protected function _generateIdentifierName($columnNames, $prefix='', $maxSize=30) + { + $hash = implode("", array_map(function ($column) { + return dechex(crc32($column)); + }, $columnNames)); + + return substr(strtoupper($prefix . "_" . $hash), 0, $maxSize); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php new file mode 100644 index 00000000..19dfa936 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php @@ -0,0 +1,1113 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Events; +use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs; +use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs; +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Base class for schema managers. Schema managers are used to inspect and/or + * modify the database schema/structure. + * + * @author Konsta Vesterinen + * @author Lukas Smith (PEAR MDB2 library) + * @author Roman Borschel + * @author Jonathan H. Wage + * @author Benjamin Eberlei + * @since 2.0 + */ +abstract class AbstractSchemaManager +{ + /** + * Holds instance of the Doctrine connection for this schema manager. + * + * @var \Doctrine\DBAL\Connection + */ + protected $_conn; + + /** + * Holds instance of the database platform used for this schema manager. + * + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + protected $_platform; + + /** + * Constructor. Accepts the Connection instance to manage the schema for. + * + * @param \Doctrine\DBAL\Connection $conn + * @param \Doctrine\DBAL\Platforms\AbstractPlatform|null $platform + */ + public function __construct(\Doctrine\DBAL\Connection $conn, AbstractPlatform $platform = null) + { + $this->_conn = $conn; + $this->_platform = $platform ?: $this->_conn->getDatabasePlatform(); + } + + /** + * Returns the associated platform. + * + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getDatabasePlatform() + { + return $this->_platform; + } + + /** + * Tries any method on the schema manager. Normally a method throws an + * exception when your DBMS doesn't support it or if an error occurs. + * This method allows you to try and method on your SchemaManager + * instance and will return false if it does not work or is not supported. + * + * + * $result = $sm->tryMethod('dropView', 'view_name'); + * + * + * @return mixed + */ + public function tryMethod() + { + $args = func_get_args(); + $method = $args[0]; + unset($args[0]); + $args = array_values($args); + + try { + return call_user_func_array(array($this, $method), $args); + } catch (\Exception $e) { + return false; + } + } + + /** + * Lists the available databases for this connection. + * + * @return array + */ + public function listDatabases() + { + $sql = $this->_platform->getListDatabasesSQL(); + + $databases = $this->_conn->fetchAll($sql); + + return $this->_getPortableDatabasesList($databases); + } + + /** + * Returns a list of all namespaces in the current database. + * + * @return array + */ + public function listNamespaceNames() + { + $sql = $this->_platform->getListNamespacesSQL(); + + $namespaces = $this->_conn->fetchAll($sql); + + return $this->getPortableNamespacesList($namespaces); + } + + /** + * Lists the available sequences for this connection. + * + * @param string|null $database + * + * @return \Doctrine\DBAL\Schema\Sequence[] + */ + public function listSequences($database = null) + { + if (is_null($database)) { + $database = $this->_conn->getDatabase(); + } + $sql = $this->_platform->getListSequencesSQL($database); + + $sequences = $this->_conn->fetchAll($sql); + + return $this->filterAssetNames($this->_getPortableSequencesList($sequences)); + } + + /** + * Lists the columns for a given table. + * + * In contrast to other libraries and to the old version of Doctrine, + * this column definition does try to contain the 'primary' field for + * the reason that it is not portable accross different RDBMS. Use + * {@see listTableIndexes($tableName)} to retrieve the primary key + * of a table. We're a RDBMS specifies more details these are held + * in the platformDetails array. + * + * @param string $table The name of the table. + * @param string|null $database + * + * @return \Doctrine\DBAL\Schema\Column[] + */ + public function listTableColumns($table, $database = null) + { + if ( ! $database) { + $database = $this->_conn->getDatabase(); + } + + $sql = $this->_platform->getListTableColumnsSQL($table, $database); + + $tableColumns = $this->_conn->fetchAll($sql); + + return $this->_getPortableTableColumnList($table, $database, $tableColumns); + } + + /** + * Lists the indexes for a given table returning an array of Index instances. + * + * Keys of the portable indexes list are all lower-cased. + * + * @param string $table The name of the table. + * + * @return \Doctrine\DBAL\Schema\Index[] + */ + public function listTableIndexes($table) + { + $sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase()); + + $tableIndexes = $this->_conn->fetchAll($sql); + + return $this->_getPortableTableIndexesList($tableIndexes, $table); + } + + /** + * Returns true if all the given tables exist. + * + * @param array $tableNames + * + * @return boolean + */ + public function tablesExist($tableNames) + { + $tableNames = array_map('strtolower', (array) $tableNames); + + return count($tableNames) == count(\array_intersect($tableNames, array_map('strtolower', $this->listTableNames()))); + } + + /** + * Returns a list of all tables in the current database. + * + * @return array + */ + public function listTableNames() + { + $sql = $this->_platform->getListTablesSQL(); + + $tables = $this->_conn->fetchAll($sql); + $tableNames = $this->_getPortableTablesList($tables); + + return $this->filterAssetNames($tableNames); + } + + /** + * Filters asset names if they are configured to return only a subset of all + * the found elements. + * + * @param array $assetNames + * + * @return array + */ + protected function filterAssetNames($assetNames) + { + $filterExpr = $this->getFilterSchemaAssetsExpression(); + if ( ! $filterExpr) { + return $assetNames; + } + + return array_values( + array_filter($assetNames, function ($assetName) use ($filterExpr) { + $assetName = ($assetName instanceof AbstractAsset) ? $assetName->getName() : $assetName; + + return preg_match($filterExpr, $assetName); + }) + ); + } + + /** + * @return string|null + */ + protected function getFilterSchemaAssetsExpression() + { + return $this->_conn->getConfiguration()->getFilterSchemaAssetsExpression(); + } + + /** + * Lists the tables for this connection. + * + * @return \Doctrine\DBAL\Schema\Table[] + */ + public function listTables() + { + $tableNames = $this->listTableNames(); + + $tables = array(); + foreach ($tableNames as $tableName) { + $tables[] = $this->listTableDetails($tableName); + } + + return $tables; + } + + /** + * @param string $tableName + * + * @return \Doctrine\DBAL\Schema\Table + */ + public function listTableDetails($tableName) + { + $columns = $this->listTableColumns($tableName); + $foreignKeys = array(); + if ($this->_platform->supportsForeignKeyConstraints()) { + $foreignKeys = $this->listTableForeignKeys($tableName); + } + $indexes = $this->listTableIndexes($tableName); + + return new Table($tableName, $columns, $indexes, $foreignKeys, false, array()); + } + + /** + * Lists the views this connection has. + * + * @return \Doctrine\DBAL\Schema\View[] + */ + public function listViews() + { + $database = $this->_conn->getDatabase(); + $sql = $this->_platform->getListViewsSQL($database); + $views = $this->_conn->fetchAll($sql); + + return $this->_getPortableViewsList($views); + } + + /** + * Lists the foreign keys for the given table. + * + * @param string $table The name of the table. + * @param string|null $database + * + * @return \Doctrine\DBAL\Schema\ForeignKeyConstraint[] + */ + public function listTableForeignKeys($table, $database = null) + { + if (is_null($database)) { + $database = $this->_conn->getDatabase(); + } + $sql = $this->_platform->getListTableForeignKeysSQL($table, $database); + $tableForeignKeys = $this->_conn->fetchAll($sql); + + return $this->_getPortableTableForeignKeysList($tableForeignKeys); + } + + /* drop*() Methods */ + + /** + * Drops a database. + * + * NOTE: You can not drop the database this SchemaManager is currently connected to. + * + * @param string $database The name of the database to drop. + * + * @return void + */ + public function dropDatabase($database) + { + $this->_execSql($this->_platform->getDropDatabaseSQL($database)); + } + + /** + * Drops the given table. + * + * @param string $tableName The name of the table to drop. + * + * @return void + */ + public function dropTable($tableName) + { + $this->_execSql($this->_platform->getDropTableSQL($tableName)); + } + + /** + * Drops the index from the given table. + * + * @param \Doctrine\DBAL\Schema\Index|string $index The name of the index. + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table. + * + * @return void + */ + public function dropIndex($index, $table) + { + if ($index instanceof Index) { + $index = $index->getQuotedName($this->_platform); + } + + $this->_execSql($this->_platform->getDropIndexSQL($index, $table)); + } + + /** + * Drops the constraint from the given table. + * + * @param \Doctrine\DBAL\Schema\Constraint $constraint + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table. + * + * @return void + */ + public function dropConstraint(Constraint $constraint, $table) + { + $this->_execSql($this->_platform->getDropConstraintSQL($constraint, $table)); + } + + /** + * Drops a foreign key from a table. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint|string $foreignKey The name of the foreign key. + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table with the foreign key. + * + * @return void + */ + public function dropForeignKey($foreignKey, $table) + { + $this->_execSql($this->_platform->getDropForeignKeySQL($foreignKey, $table)); + } + + /** + * Drops a sequence with a given name. + * + * @param string $name The name of the sequence to drop. + * + * @return void + */ + public function dropSequence($name) + { + $this->_execSql($this->_platform->getDropSequenceSQL($name)); + } + + /** + * Drops a view. + * + * @param string $name The name of the view. + * + * @return void + */ + public function dropView($name) + { + $this->_execSql($this->_platform->getDropViewSQL($name)); + } + + /* create*() Methods */ + + /** + * Creates a new database. + * + * @param string $database The name of the database to create. + * + * @return void + */ + public function createDatabase($database) + { + $this->_execSql($this->_platform->getCreateDatabaseSQL($database)); + } + + /** + * Creates a new table. + * + * @param \Doctrine\DBAL\Schema\Table $table + * + * @return void + */ + public function createTable(Table $table) + { + $createFlags = AbstractPlatform::CREATE_INDEXES|AbstractPlatform::CREATE_FOREIGNKEYS; + $this->_execSql($this->_platform->getCreateTableSQL($table, $createFlags)); + } + + /** + * Creates a new sequence. + * + * @param \Doctrine\DBAL\Schema\Sequence $sequence + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException If something fails at database level. + */ + public function createSequence($sequence) + { + $this->_execSql($this->_platform->getCreateSequenceSQL($sequence)); + } + + /** + * Creates a constraint on a table. + * + * @param \Doctrine\DBAL\Schema\Constraint $constraint + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return void + */ + public function createConstraint(Constraint $constraint, $table) + { + $this->_execSql($this->_platform->getCreateConstraintSQL($constraint, $table)); + } + + /** + * Creates a new index on a table. + * + * @param \Doctrine\DBAL\Schema\Index $index + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table on which the index is to be created. + * + * @return void + */ + public function createIndex(Index $index, $table) + { + $this->_execSql($this->_platform->getCreateIndexSQL($index, $table)); + } + + /** + * Creates a new foreign key. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey The ForeignKey instance. + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table on which the foreign key is to be created. + * + * @return void + */ + public function createForeignKey(ForeignKeyConstraint $foreignKey, $table) + { + $this->_execSql($this->_platform->getCreateForeignKeySQL($foreignKey, $table)); + } + + /** + * Creates a new view. + * + * @param \Doctrine\DBAL\Schema\View $view + * + * @return void + */ + public function createView(View $view) + { + $this->_execSql($this->_platform->getCreateViewSQL($view->getQuotedName($this->_platform), $view->getSql())); + } + + /* dropAndCreate*() Methods */ + + /** + * Drops and creates a constraint. + * + * @see dropConstraint() + * @see createConstraint() + * + * @param \Doctrine\DBAL\Schema\Constraint $constraint + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return void + */ + public function dropAndCreateConstraint(Constraint $constraint, $table) + { + $this->tryMethod('dropConstraint', $constraint, $table); + $this->createConstraint($constraint, $table); + } + + /** + * Drops and creates a new index on a table. + * + * @param \Doctrine\DBAL\Schema\Index $index + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table on which the index is to be created. + * + * @return void + */ + public function dropAndCreateIndex(Index $index, $table) + { + $this->tryMethod('dropIndex', $index->getQuotedName($this->_platform), $table); + $this->createIndex($index, $table); + } + + /** + * Drops and creates a new foreign key. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey An associative array that defines properties of the foreign key to be created. + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table on which the foreign key is to be created. + * + * @return void + */ + public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table) + { + $this->tryMethod('dropForeignKey', $foreignKey, $table); + $this->createForeignKey($foreignKey, $table); + } + + /** + * Drops and create a new sequence. + * + * @param \Doctrine\DBAL\Schema\Sequence $sequence + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException If something fails at database level. + */ + public function dropAndCreateSequence(Sequence $sequence) + { + $this->tryMethod('dropSequence', $sequence->getQuotedName($this->_platform)); + $this->createSequence($sequence); + } + + /** + * Drops and creates a new table. + * + * @param \Doctrine\DBAL\Schema\Table $table + * + * @return void + */ + public function dropAndCreateTable(Table $table) + { + $this->tryMethod('dropTable', $table->getQuotedName($this->_platform)); + $this->createTable($table); + } + + /** + * Drops and creates a new database. + * + * @param string $database The name of the database to create. + * + * @return void + */ + public function dropAndCreateDatabase($database) + { + $this->tryMethod('dropDatabase', $database); + $this->createDatabase($database); + } + + /** + * Drops and creates a new view. + * + * @param \Doctrine\DBAL\Schema\View $view + * + * @return void + */ + public function dropAndCreateView(View $view) + { + $this->tryMethod('dropView', $view->getQuotedName($this->_platform)); + $this->createView($view); + } + + /* alterTable() Methods */ + + /** + * Alters an existing tables schema. + * + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + * + * @return void + */ + public function alterTable(TableDiff $tableDiff) + { + $queries = $this->_platform->getAlterTableSQL($tableDiff); + if (is_array($queries) && count($queries)) { + foreach ($queries as $ddlQuery) { + $this->_execSql($ddlQuery); + } + } + } + + /** + * Renames a given table to another name. + * + * @param string $name The current name of the table. + * @param string $newName The new name of the table. + * + * @return void + */ + public function renameTable($name, $newName) + { + $tableDiff = new TableDiff($name); + $tableDiff->newName = $newName; + $this->alterTable($tableDiff); + } + + /** + * Methods for filtering return values of list*() methods to convert + * the native DBMS data definition to a portable Doctrine definition + */ + + /** + * @param array $databases + * + * @return array + */ + protected function _getPortableDatabasesList($databases) + { + $list = array(); + foreach ($databases as $value) { + if ($value = $this->_getPortableDatabaseDefinition($value)) { + $list[] = $value; + } + } + + return $list; + } + + /** + * Converts a list of namespace names from the native DBMS data definition to a portable Doctrine definition. + * + * @param array $namespaces The list of namespace names in the native DBMS data definition. + * + * @return array + */ + protected function getPortableNamespacesList(array $namespaces) + { + $namespacesList = array(); + + foreach ($namespaces as $namespace) { + $namespacesList[] = $this->getPortableNamespaceDefinition($namespace); + } + + return $namespacesList; + } + + /** + * @param array $database + * + * @return mixed + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database; + } + + /** + * Converts a namespace definition from the native DBMS data definition to a portable Doctrine definition. + * + * @param array $namespace The native DBMS namespace definition. + * + * @return mixed + */ + protected function getPortableNamespaceDefinition(array $namespace) + { + return $namespace; + } + + /** + * @param array $functions + * + * @return array + */ + protected function _getPortableFunctionsList($functions) + { + $list = array(); + foreach ($functions as $value) { + if ($value = $this->_getPortableFunctionDefinition($value)) { + $list[] = $value; + } + } + + return $list; + } + + /** + * @param array $function + * + * @return mixed + */ + protected function _getPortableFunctionDefinition($function) + { + return $function; + } + + /** + * @param array $triggers + * + * @return array + */ + protected function _getPortableTriggersList($triggers) + { + $list = array(); + foreach ($triggers as $value) { + if ($value = $this->_getPortableTriggerDefinition($value)) { + $list[] = $value; + } + } + + return $list; + } + + /** + * @param array $trigger + * + * @return mixed + */ + protected function _getPortableTriggerDefinition($trigger) + { + return $trigger; + } + + /** + * @param array $sequences + * + * @return array + */ + protected function _getPortableSequencesList($sequences) + { + $list = array(); + foreach ($sequences as $value) { + if ($value = $this->_getPortableSequenceDefinition($value)) { + $list[] = $value; + } + } + + return $list; + } + + /** + * @param array $sequence + * + * @return \Doctrine\DBAL\Schema\Sequence + * + * @throws \Doctrine\DBAL\DBALException + */ + protected function _getPortableSequenceDefinition($sequence) + { + throw DBALException::notSupported('Sequences'); + } + + /** + * Independent of the database the keys of the column list result are lowercased. + * + * The name of the created column instance however is kept in its case. + * + * @param string $table The name of the table. + * @param string $database + * @param array $tableColumns + * + * @return array + */ + protected function _getPortableTableColumnList($table, $database, $tableColumns) + { + $eventManager = $this->_platform->getEventManager(); + + $list = array(); + foreach ($tableColumns as $tableColumn) { + $column = null; + $defaultPrevented = false; + + if (null !== $eventManager && $eventManager->hasListeners(Events::onSchemaColumnDefinition)) { + $eventArgs = new SchemaColumnDefinitionEventArgs($tableColumn, $table, $database, $this->_conn); + $eventManager->dispatchEvent(Events::onSchemaColumnDefinition, $eventArgs); + + $defaultPrevented = $eventArgs->isDefaultPrevented(); + $column = $eventArgs->getColumn(); + } + + if ( ! $defaultPrevented) { + $column = $this->_getPortableTableColumnDefinition($tableColumn); + } + + if ($column) { + $name = strtolower($column->getQuotedName($this->_platform)); + $list[$name] = $column; + } + } + + return $list; + } + + /** + * Gets Table Column Definition. + * + * @param array $tableColumn + * + * @return \Doctrine\DBAL\Schema\Column + */ + abstract protected function _getPortableTableColumnDefinition($tableColumn); + + /** + * Aggregates and groups the index results according to the required data result. + * + * @param array $tableIndexRows + * @param string|null $tableName + * + * @return array + */ + protected function _getPortableTableIndexesList($tableIndexRows, $tableName=null) + { + $result = array(); + foreach ($tableIndexRows as $tableIndex) { + $indexName = $keyName = $tableIndex['key_name']; + if ($tableIndex['primary']) { + $keyName = 'primary'; + } + $keyName = strtolower($keyName); + + if (!isset($result[$keyName])) { + $result[$keyName] = array( + 'name' => $indexName, + 'columns' => array($tableIndex['column_name']), + 'unique' => $tableIndex['non_unique'] ? false : true, + 'primary' => $tableIndex['primary'], + 'flags' => isset($tableIndex['flags']) ? $tableIndex['flags'] : array(), + 'options' => isset($tableIndex['where']) ? array('where' => $tableIndex['where']) : array(), + ); + } else { + $result[$keyName]['columns'][] = $tableIndex['column_name']; + } + } + + $eventManager = $this->_platform->getEventManager(); + + $indexes = array(); + foreach ($result as $indexKey => $data) { + $index = null; + $defaultPrevented = false; + + if (null !== $eventManager && $eventManager->hasListeners(Events::onSchemaIndexDefinition)) { + $eventArgs = new SchemaIndexDefinitionEventArgs($data, $tableName, $this->_conn); + $eventManager->dispatchEvent(Events::onSchemaIndexDefinition, $eventArgs); + + $defaultPrevented = $eventArgs->isDefaultPrevented(); + $index = $eventArgs->getIndex(); + } + + if ( ! $defaultPrevented) { + $index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags'], $data['options']); + } + + if ($index) { + $indexes[$indexKey] = $index; + } + } + + return $indexes; + } + + /** + * @param array $tables + * + * @return array + */ + protected function _getPortableTablesList($tables) + { + $list = array(); + foreach ($tables as $value) { + if ($value = $this->_getPortableTableDefinition($value)) { + $list[] = $value; + } + } + + return $list; + } + + /** + * @param array $table + * + * @return array + */ + protected function _getPortableTableDefinition($table) + { + return $table; + } + + /** + * @param array $users + * + * @return array + */ + protected function _getPortableUsersList($users) + { + $list = array(); + foreach ($users as $value) { + if ($value = $this->_getPortableUserDefinition($value)) { + $list[] = $value; + } + } + + return $list; + } + + /** + * @param array $user + * + * @return mixed + */ + protected function _getPortableUserDefinition($user) + { + return $user; + } + + /** + * @param array $views + * + * @return array + */ + protected function _getPortableViewsList($views) + { + $list = array(); + foreach ($views as $value) { + if ($view = $this->_getPortableViewDefinition($value)) { + $viewName = strtolower($view->getQuotedName($this->_platform)); + $list[$viewName] = $view; + } + } + + return $list; + } + + /** + * @param array $view + * + * @return mixed + */ + protected function _getPortableViewDefinition($view) + { + return false; + } + + /** + * @param array $tableForeignKeys + * + * @return array + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $list = array(); + foreach ($tableForeignKeys as $value) { + if ($value = $this->_getPortableTableForeignKeyDefinition($value)) { + $list[] = $value; + } + } + + return $list; + } + + /** + * @param array $tableForeignKey + * + * @return mixed + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + return $tableForeignKey; + } + + /** + * @param array|string $sql + * + * @return void + */ + protected function _execSql($sql) + { + foreach ((array) $sql as $query) { + $this->_conn->executeUpdate($query); + } + } + + /** + * Creates a schema instance for the current database. + * + * @return \Doctrine\DBAL\Schema\Schema + */ + public function createSchema() + { + $namespaces = array(); + + if ($this->_platform->supportsSchemas()) { + $namespaces = $this->listNamespaceNames(); + } + + $sequences = array(); + + if ($this->_platform->supportsSequences()) { + $sequences = $this->listSequences(); + } + + $tables = $this->listTables(); + + return new Schema($tables, $sequences, $this->createSchemaConfig(), $namespaces); + } + + /** + * Creates the configuration for this schema. + * + * @return \Doctrine\DBAL\Schema\SchemaConfig + */ + public function createSchemaConfig() + { + $schemaConfig = new SchemaConfig(); + $schemaConfig->setMaxIdentifierLength($this->_platform->getMaxIdentifierLength()); + + $searchPaths = $this->getSchemaSearchPaths(); + if (isset($searchPaths[0])) { + $schemaConfig->setName($searchPaths[0]); + } + + $params = $this->_conn->getParams(); + if (isset($params['defaultTableOptions'])) { + $schemaConfig->setDefaultTableOptions($params['defaultTableOptions']); + } + + return $schemaConfig; + } + + /** + * The search path for namespaces in the currently connected database. + * + * The first entry is usually the default namespace in the Schema. All + * further namespaces contain tables/sequences which can also be addressed + * with a short, not full-qualified name. + * + * For databases that don't support subschema/namespaces this method + * returns the name of the currently connected database. + * + * @return array + */ + public function getSchemaSearchPaths() + { + return array($this->_conn->getDatabase()); + } + + /** + * Given a table comment this method tries to extract a typehint for Doctrine Type, or returns + * the type given as default. + * + * @param string $comment + * @param string $currentType + * + * @return string + */ + public function extractDoctrineTypeFromComment($comment, $currentType) + { + if (preg_match("(\(DC2Type:([a-zA-Z0-9_]+)\))", $comment, $match)) { + $currentType = $match[1]; + } + + return $currentType; + } + + /** + * @param string $comment + * @param string $type + * + * @return string + */ + public function removeDoctrineTypeFromComment($comment, $type) + { + return str_replace('(DC2Type:'.$type.')', '', $comment); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Column.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Column.php new file mode 100644 index 00000000..192ce1d2 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Column.php @@ -0,0 +1,487 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Types\Type; + +/** + * Object representation of a database column. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class Column extends AbstractAsset +{ + /** + * @var Type + */ + protected $_type; + + /** + * @var integer|null + */ + protected $_length = null; + + /** + * @var integer + */ + protected $_precision = 10; + + /** + * @var integer + */ + protected $_scale = 0; + + /** + * @var boolean + */ + protected $_unsigned = false; + + /** + * @var boolean + */ + protected $_fixed = false; + + /** + * @var boolean + */ + protected $_notnull = true; + + /** + * @var string|null + */ + protected $_default = null; + + /** + * @var boolean + */ + protected $_autoincrement = false; + + /** + * @var array + */ + protected $_platformOptions = array(); + + /** + * @var string|null + */ + protected $_columnDefinition = null; + + /** + * @var string|null + */ + protected $_comment = null; + + /** + * @var array + */ + protected $_customSchemaOptions = array(); + + /** + * Creates a new Column. + * + * @param string $columnName + * @param Type $type + * @param array $options + */ + public function __construct($columnName, Type $type, array $options=array()) + { + $this->_setName($columnName); + $this->setType($type); + $this->setOptions($options); + } + + /** + * @param array $options + * + * @return Column + */ + public function setOptions(array $options) + { + foreach ($options as $name => $value) { + $method = "set".$name; + if (method_exists($this, $method)) { + $this->$method($value); + } + } + + return $this; + } + + /** + * @param Type $type + * + * @return Column + */ + public function setType(Type $type) + { + $this->_type = $type; + + return $this; + } + + /** + * @param integer|null $length + * + * @return Column + */ + public function setLength($length) + { + if ($length !== null) { + $this->_length = (int) $length; + } else { + $this->_length = null; + } + + return $this; + } + + /** + * @param integer $precision + * + * @return Column + */ + public function setPrecision($precision) + { + if (!is_numeric($precision)) { + $precision = 10; // defaults to 10 when no valid precision is given. + } + + $this->_precision = (int) $precision; + + return $this; + } + + /** + * @param integer $scale + * + * @return Column + */ + public function setScale($scale) + { + if (!is_numeric($scale)) { + $scale = 0; + } + + $this->_scale = (int) $scale; + + return $this; + } + + /** + * @param boolean $unsigned + * + * @return Column + */ + public function setUnsigned($unsigned) + { + $this->_unsigned = (bool) $unsigned; + + return $this; + } + + /** + * @param boolean $fixed + * + * @return Column + */ + public function setFixed($fixed) + { + $this->_fixed = (bool) $fixed; + + return $this; + } + + /** + * @param boolean $notnull + * + * @return Column + */ + public function setNotnull($notnull) + { + $this->_notnull = (bool) $notnull; + + return $this; + } + + /** + * @param mixed $default + * + * @return Column + */ + public function setDefault($default) + { + $this->_default = $default; + + return $this; + } + + /** + * @param array $platformOptions + * + * @return Column + */ + public function setPlatformOptions(array $platformOptions) + { + $this->_platformOptions = $platformOptions; + + return $this; + } + + /** + * @param string $name + * @param mixed $value + * + * @return Column + */ + public function setPlatformOption($name, $value) + { + $this->_platformOptions[$name] = $value; + + return $this; + } + + /** + * @param string $value + * + * @return Column + */ + public function setColumnDefinition($value) + { + $this->_columnDefinition = $value; + + return $this; + } + + /** + * @return Type + */ + public function getType() + { + return $this->_type; + } + + /** + * @return integer|null + */ + public function getLength() + { + return $this->_length; + } + + /** + * @return integer + */ + public function getPrecision() + { + return $this->_precision; + } + + /** + * @return integer + */ + public function getScale() + { + return $this->_scale; + } + + /** + * @return boolean + */ + public function getUnsigned() + { + return $this->_unsigned; + } + + /** + * @return boolean + */ + public function getFixed() + { + return $this->_fixed; + } + + /** + * @return boolean + */ + public function getNotnull() + { + return $this->_notnull; + } + + /** + * @return string|null + */ + public function getDefault() + { + return $this->_default; + } + + /** + * @return array + */ + public function getPlatformOptions() + { + return $this->_platformOptions; + } + + /** + * @param string $name + * + * @return boolean + */ + public function hasPlatformOption($name) + { + return isset($this->_platformOptions[$name]); + } + + /** + * @param string $name + * + * @return mixed + */ + public function getPlatformOption($name) + { + return $this->_platformOptions[$name]; + } + + /** + * @return string|null + */ + public function getColumnDefinition() + { + return $this->_columnDefinition; + } + + /** + * @return boolean + */ + public function getAutoincrement() + { + return $this->_autoincrement; + } + + /** + * @param boolean $flag + * + * @return Column + */ + public function setAutoincrement($flag) + { + $this->_autoincrement = $flag; + + return $this; + } + + /** + * @param string $comment + * + * @return Column + */ + public function setComment($comment) + { + $this->_comment = $comment; + + return $this; + } + + /** + * @return string|null + */ + public function getComment() + { + return $this->_comment; + } + + /** + * @param string $name + * @param mixed $value + * + * @return Column + */ + public function setCustomSchemaOption($name, $value) + { + $this->_customSchemaOptions[$name] = $value; + + return $this; + } + + /** + * @param string $name + * + * @return boolean + */ + public function hasCustomSchemaOption($name) + { + return isset($this->_customSchemaOptions[$name]); + } + + /** + * @param string $name + * + * @return mixed + */ + public function getCustomSchemaOption($name) + { + return $this->_customSchemaOptions[$name]; + } + + /** + * @param array $customSchemaOptions + * + * @return Column + */ + public function setCustomSchemaOptions(array $customSchemaOptions) + { + $this->_customSchemaOptions = $customSchemaOptions; + + return $this; + } + + /** + * @return array + */ + public function getCustomSchemaOptions() + { + return $this->_customSchemaOptions; + } + + /** + * @return array + */ + public function toArray() + { + return array_merge(array( + 'name' => $this->_name, + 'type' => $this->_type, + 'default' => $this->_default, + 'notnull' => $this->_notnull, + 'length' => $this->_length, + 'precision' => $this->_precision, + 'scale' => $this->_scale, + 'fixed' => $this->_fixed, + 'unsigned' => $this->_unsigned, + 'autoincrement' => $this->_autoincrement, + 'columnDefinition' => $this->_columnDefinition, + 'comment' => $this->_comment, + ), $this->_platformOptions, $this->_customSchemaOptions); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/ColumnDiff.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/ColumnDiff.php new file mode 100644 index 00000000..61d7f731 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/ColumnDiff.php @@ -0,0 +1,84 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * Represents the change of a column. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class ColumnDiff +{ + /** + * @var string + */ + public $oldColumnName; + + /** + * @var Column + */ + public $column; + + /** + * @var array + */ + public $changedProperties = array(); + + /** + * @var Column + */ + public $fromColumn; + + /** + * @param string $oldColumnName + * @param Column $column + * @param string[] $changedProperties + * @param Column $fromColumn + */ + public function __construct($oldColumnName, Column $column, array $changedProperties = array(), Column $fromColumn = null) + { + $this->oldColumnName = $oldColumnName; + $this->column = $column; + $this->changedProperties = $changedProperties; + $this->fromColumn = $fromColumn; + } + + /** + * @param string $propertyName + * + * @return boolean + */ + public function hasChanged($propertyName) + { + return in_array($propertyName, $this->changedProperties); + } + + /** + * @return Identifier + */ + public function getOldColumnName() + { + $quote = $this->fromColumn && $this->fromColumn->isQuoted(); + + return new Identifier($this->oldColumnName, $quote); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Comparator.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Comparator.php new file mode 100644 index 00000000..abedfc06 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Comparator.php @@ -0,0 +1,519 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Types; + +/** + * Compares two Schemas and return an instance of SchemaDiff. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class Comparator +{ + /** + * @param \Doctrine\DBAL\Schema\Schema $fromSchema + * @param \Doctrine\DBAL\Schema\Schema $toSchema + * + * @return \Doctrine\DBAL\Schema\SchemaDiff + */ + static public function compareSchemas(Schema $fromSchema, Schema $toSchema) + { + $c = new self(); + + return $c->compare($fromSchema, $toSchema); + } + + /** + * Returns a SchemaDiff object containing the differences between the schemas $fromSchema and $toSchema. + * + * The returned differences are returned in such a way that they contain the + * operations to change the schema stored in $fromSchema to the schema that is + * stored in $toSchema. + * + * @param \Doctrine\DBAL\Schema\Schema $fromSchema + * @param \Doctrine\DBAL\Schema\Schema $toSchema + * + * @return \Doctrine\DBAL\Schema\SchemaDiff + */ + public function compare(Schema $fromSchema, Schema $toSchema) + { + $diff = new SchemaDiff(); + $diff->fromSchema = $fromSchema; + + $foreignKeysToTable = array(); + + foreach ($toSchema->getNamespaces() as $namespace) { + if ( ! $fromSchema->hasNamespace($namespace)) { + $diff->newNamespaces[$namespace] = $namespace; + } + } + + foreach ($fromSchema->getNamespaces() as $namespace) { + if ( ! $toSchema->hasNamespace($namespace)) { + $diff->removedNamespaces[$namespace] = $namespace; + } + } + + foreach ($toSchema->getTables() as $table) { + $tableName = $table->getShortestName($toSchema->getName()); + if ( ! $fromSchema->hasTable($tableName)) { + $diff->newTables[$tableName] = $toSchema->getTable($tableName); + } else { + $tableDifferences = $this->diffTable($fromSchema->getTable($tableName), $toSchema->getTable($tableName)); + if ($tableDifferences !== false) { + $diff->changedTables[$tableName] = $tableDifferences; + } + } + } + + /* Check if there are tables removed */ + foreach ($fromSchema->getTables() as $table) { + $tableName = $table->getShortestName($fromSchema->getName()); + + $table = $fromSchema->getTable($tableName); + if ( ! $toSchema->hasTable($tableName)) { + $diff->removedTables[$tableName] = $table; + } + + // also remember all foreign keys that point to a specific table + foreach ($table->getForeignKeys() as $foreignKey) { + $foreignTable = strtolower($foreignKey->getForeignTableName()); + if (!isset($foreignKeysToTable[$foreignTable])) { + $foreignKeysToTable[$foreignTable] = array(); + } + $foreignKeysToTable[$foreignTable][] = $foreignKey; + } + } + + foreach ($diff->removedTables as $tableName => $table) { + if (isset($foreignKeysToTable[$tableName])) { + $diff->orphanedForeignKeys = array_merge($diff->orphanedForeignKeys, $foreignKeysToTable[$tableName]); + + // deleting duplicated foreign keys present on both on the orphanedForeignKey + // and the removedForeignKeys from changedTables + foreach ($foreignKeysToTable[$tableName] as $foreignKey) { + // strtolower the table name to make if compatible with getShortestName + $localTableName = strtolower($foreignKey->getLocalTableName()); + if (isset($diff->changedTables[$localTableName])) { + foreach ($diff->changedTables[$localTableName]->removedForeignKeys as $key => $removedForeignKey) { + // We check if the key is from the removed table if not we skip. + if ($tableName !== strtolower($removedForeignKey->getForeignTableName())) { + continue; + } + unset($diff->changedTables[$localTableName]->removedForeignKeys[$key]); + } + } + } + } + } + + foreach ($toSchema->getSequences() as $sequence) { + $sequenceName = $sequence->getShortestName($toSchema->getName()); + if ( ! $fromSchema->hasSequence($sequenceName)) { + if ( ! $this->isAutoIncrementSequenceInSchema($fromSchema, $sequence)) { + $diff->newSequences[] = $sequence; + } + } else { + if ($this->diffSequence($sequence, $fromSchema->getSequence($sequenceName))) { + $diff->changedSequences[] = $toSchema->getSequence($sequenceName); + } + } + } + + foreach ($fromSchema->getSequences() as $sequence) { + if ($this->isAutoIncrementSequenceInSchema($toSchema, $sequence)) { + continue; + } + + $sequenceName = $sequence->getShortestName($fromSchema->getName()); + + if ( ! $toSchema->hasSequence($sequenceName)) { + $diff->removedSequences[] = $sequence; + } + } + + return $diff; + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + * @param \Doctrine\DBAL\Schema\Sequence $sequence + * + * @return boolean + */ + private function isAutoIncrementSequenceInSchema($schema, $sequence) + { + foreach ($schema->getTables() as $table) { + if ($sequence->isAutoIncrementsFor($table)) { + return true; + } + } + + return false; + } + + /** + * @param \Doctrine\DBAL\Schema\Sequence $sequence1 + * @param \Doctrine\DBAL\Schema\Sequence $sequence2 + * + * @return boolean + */ + public function diffSequence(Sequence $sequence1, Sequence $sequence2) + { + if ($sequence1->getAllocationSize() != $sequence2->getAllocationSize()) { + return true; + } + + if ($sequence1->getInitialValue() != $sequence2->getInitialValue()) { + return true; + } + + return false; + } + + /** + * Returns the difference between the tables $table1 and $table2. + * + * If there are no differences this method returns the boolean false. + * + * @param \Doctrine\DBAL\Schema\Table $table1 + * @param \Doctrine\DBAL\Schema\Table $table2 + * + * @return boolean|\Doctrine\DBAL\Schema\TableDiff + */ + public function diffTable(Table $table1, Table $table2) + { + $changes = 0; + $tableDifferences = new TableDiff($table1->getName()); + $tableDifferences->fromTable = $table1; + + $table1Columns = $table1->getColumns(); + $table2Columns = $table2->getColumns(); + + /* See if all the fields in table 1 exist in table 2 */ + foreach ($table2Columns as $columnName => $column) { + if ( !$table1->hasColumn($columnName)) { + $tableDifferences->addedColumns[$columnName] = $column; + $changes++; + } + } + /* See if there are any removed fields in table 2 */ + foreach ($table1Columns as $columnName => $column) { + // See if column is removed in table 2. + if ( ! $table2->hasColumn($columnName)) { + $tableDifferences->removedColumns[$columnName] = $column; + $changes++; + continue; + } + + // See if column has changed properties in table 2. + $changedProperties = $this->diffColumn($column, $table2->getColumn($columnName)); + + if ( ! empty($changedProperties)) { + $columnDiff = new ColumnDiff($column->getName(), $table2->getColumn($columnName), $changedProperties); + $columnDiff->fromColumn = $column; + $tableDifferences->changedColumns[$column->getName()] = $columnDiff; + $changes++; + } + } + + $this->detectColumnRenamings($tableDifferences); + + $table1Indexes = $table1->getIndexes(); + $table2Indexes = $table2->getIndexes(); + + /* See if all the indexes in table 1 exist in table 2 */ + foreach ($table2Indexes as $indexName => $index) { + if (($index->isPrimary() && $table1->hasPrimaryKey()) || $table1->hasIndex($indexName)) { + continue; + } + + $tableDifferences->addedIndexes[$indexName] = $index; + $changes++; + } + /* See if there are any removed indexes in table 2 */ + foreach ($table1Indexes as $indexName => $index) { + // See if index is removed in table 2. + if (($index->isPrimary() && ! $table2->hasPrimaryKey()) || + ! $index->isPrimary() && ! $table2->hasIndex($indexName) + ) { + $tableDifferences->removedIndexes[$indexName] = $index; + $changes++; + continue; + } + + // See if index has changed in table 2. + $table2Index = $index->isPrimary() ? $table2->getPrimaryKey() : $table2->getIndex($indexName); + + if ($this->diffIndex($index, $table2Index)) { + $tableDifferences->changedIndexes[$indexName] = $table2Index; + $changes++; + } + } + + $this->detectIndexRenamings($tableDifferences); + + $fromFkeys = $table1->getForeignKeys(); + $toFkeys = $table2->getForeignKeys(); + + foreach ($fromFkeys as $key1 => $constraint1) { + foreach ($toFkeys as $key2 => $constraint2) { + if ($this->diffForeignKey($constraint1, $constraint2) === false) { + unset($fromFkeys[$key1]); + unset($toFkeys[$key2]); + } else { + if (strtolower($constraint1->getName()) == strtolower($constraint2->getName())) { + $tableDifferences->changedForeignKeys[] = $constraint2; + $changes++; + unset($fromFkeys[$key1]); + unset($toFkeys[$key2]); + } + } + } + } + + foreach ($fromFkeys as $constraint1) { + $tableDifferences->removedForeignKeys[] = $constraint1; + $changes++; + } + + foreach ($toFkeys as $constraint2) { + $tableDifferences->addedForeignKeys[] = $constraint2; + $changes++; + } + + return $changes ? $tableDifferences : false; + } + + /** + * Try to find columns that only changed their name, rename operations maybe cheaper than add/drop + * however ambiguities between different possibilities should not lead to renaming at all. + * + * @param \Doctrine\DBAL\Schema\TableDiff $tableDifferences + * + * @return void + */ + private function detectColumnRenamings(TableDiff $tableDifferences) + { + $renameCandidates = array(); + foreach ($tableDifferences->addedColumns as $addedColumnName => $addedColumn) { + foreach ($tableDifferences->removedColumns as $removedColumn) { + if (count($this->diffColumn($addedColumn, $removedColumn)) == 0) { + $renameCandidates[$addedColumn->getName()][] = array($removedColumn, $addedColumn, $addedColumnName); + } + } + } + + foreach ($renameCandidates as $candidateColumns) { + if (count($candidateColumns) == 1) { + list($removedColumn, $addedColumn) = $candidateColumns[0]; + $removedColumnName = strtolower($removedColumn->getName()); + $addedColumnName = strtolower($addedColumn->getName()); + + if ( ! isset($tableDifferences->renamedColumns[$removedColumnName])) { + $tableDifferences->renamedColumns[$removedColumnName] = $addedColumn; + unset($tableDifferences->addedColumns[$addedColumnName]); + unset($tableDifferences->removedColumns[$removedColumnName]); + } + } + } + } + + /** + * Try to find indexes that only changed their name, rename operations maybe cheaper than add/drop + * however ambiguities between different possibilities should not lead to renaming at all. + * + * @param \Doctrine\DBAL\Schema\TableDiff $tableDifferences + * + * @return void + */ + private function detectIndexRenamings(TableDiff $tableDifferences) + { + $renameCandidates = array(); + + // Gather possible rename candidates by comparing each added and removed index based on semantics. + foreach ($tableDifferences->addedIndexes as $addedIndexName => $addedIndex) { + foreach ($tableDifferences->removedIndexes as $removedIndex) { + if (! $this->diffIndex($addedIndex, $removedIndex)) { + $renameCandidates[$addedIndex->getName()][] = array($removedIndex, $addedIndex, $addedIndexName); + } + } + } + + foreach ($renameCandidates as $candidateIndexes) { + // If the current rename candidate contains exactly one semantically equal index, + // we can safely rename it. + // Otherwise it is unclear if a rename action is really intended, + // therefore we let those ambiguous indexes be added/dropped. + if (count($candidateIndexes) === 1) { + list($removedIndex, $addedIndex) = $candidateIndexes[0]; + + $removedIndexName = strtolower($removedIndex->getName()); + $addedIndexName = strtolower($addedIndex->getName()); + + if (! isset($tableDifferences->renamedIndexes[$removedIndexName])) { + $tableDifferences->renamedIndexes[$removedIndexName] = $addedIndex; + unset($tableDifferences->addedIndexes[$addedIndexName]); + unset($tableDifferences->removedIndexes[$removedIndexName]); + } + } + } + } + + /** + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $key1 + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $key2 + * + * @return boolean + */ + public function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2) + { + if (array_map('strtolower', $key1->getUnquotedLocalColumns()) != array_map('strtolower', $key2->getUnquotedLocalColumns())) { + return true; + } + + if (array_map('strtolower', $key1->getUnquotedForeignColumns()) != array_map('strtolower', $key2->getUnquotedForeignColumns())) { + return true; + } + + if ($key1->getUnqualifiedForeignTableName() !== $key2->getUnqualifiedForeignTableName()) { + return true; + } + + if ($key1->onUpdate() != $key2->onUpdate()) { + return true; + } + + if ($key1->onDelete() != $key2->onDelete()) { + return true; + } + + return false; + } + + /** + * Returns the difference between the fields $field1 and $field2. + * + * If there are differences this method returns $field2, otherwise the + * boolean false. + * + * @param \Doctrine\DBAL\Schema\Column $column1 + * @param \Doctrine\DBAL\Schema\Column $column2 + * + * @return array + */ + public function diffColumn(Column $column1, Column $column2) + { + $properties1 = $column1->toArray(); + $properties2 = $column2->toArray(); + + $changedProperties = array(); + + foreach (array('type', 'notnull', 'unsigned', 'autoincrement') as $property) { + if ($properties1[$property] != $properties2[$property]) { + $changedProperties[] = $property; + } + } + + if ($properties1['default'] != $properties2['default'] || + // Null values need to be checked additionally as they tell whether to create or drop a default value. + // null != 0, null != false, null != '' etc. This affects platform's table alteration SQL generation. + (null === $properties1['default'] && null !== $properties2['default']) || + (null === $properties2['default'] && null !== $properties1['default']) + ) { + $changedProperties[] = 'default'; + } + + if (($properties1['type'] instanceof Types\StringType && ! $properties1['type'] instanceof Types\GuidType) || + $properties1['type'] instanceof Types\BinaryType + ) { + // check if value of length is set at all, default value assumed otherwise. + $length1 = $properties1['length'] ?: 255; + $length2 = $properties2['length'] ?: 255; + if ($length1 != $length2) { + $changedProperties[] = 'length'; + } + + if ($properties1['fixed'] != $properties2['fixed']) { + $changedProperties[] = 'fixed'; + } + } elseif ($properties1['type'] instanceof Types\DecimalType) { + if (($properties1['precision'] ?: 10) != ($properties2['precision'] ?: 10)) { + $changedProperties[] = 'precision'; + } + if ($properties1['scale'] != $properties2['scale']) { + $changedProperties[] = 'scale'; + } + } + + // A null value and an empty string are actually equal for a comment so they should not trigger a change. + if ($properties1['comment'] !== $properties2['comment'] && + ! (null === $properties1['comment'] && '' === $properties2['comment']) && + ! (null === $properties2['comment'] && '' === $properties1['comment']) + ) { + $changedProperties[] = 'comment'; + } + + $customOptions1 = $column1->getCustomSchemaOptions(); + $customOptions2 = $column2->getCustomSchemaOptions(); + + foreach (array_merge(array_keys($customOptions1), array_keys($customOptions2)) as $key) { + if ( ! array_key_exists($key, $properties1) || ! array_key_exists($key, $properties2)) { + $changedProperties[] = $key; + } elseif ($properties1[$key] !== $properties2[$key]) { + $changedProperties[] = $key; + } + } + + $platformOptions1 = $column1->getPlatformOptions(); + $platformOptions2 = $column2->getPlatformOptions(); + + foreach (array_keys(array_intersect_key($platformOptions1, $platformOptions2)) as $key) { + if ($properties1[$key] !== $properties2[$key]) { + $changedProperties[] = $key; + } + } + + return array_unique($changedProperties); + } + + /** + * Finds the difference between the indexes $index1 and $index2. + * + * Compares $index1 with $index2 and returns $index2 if there are any + * differences or false in case there are no differences. + * + * @param \Doctrine\DBAL\Schema\Index $index1 + * @param \Doctrine\DBAL\Schema\Index $index2 + * + * @return boolean + */ + public function diffIndex(Index $index1, Index $index2) + { + if ($index1->isFullfilledBy($index2) && $index2->isFullfilledBy($index1)) { + return false; + } + + return true; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Constraint.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Constraint.php new file mode 100644 index 00000000..c373472c --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Constraint.php @@ -0,0 +1,66 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Marker interface for contraints. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +interface Constraint +{ + /** + * @return string + */ + public function getName(); + + /** + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return string + */ + public function getQuotedName(AbstractPlatform $platform); + + /** + * Returns the names of the referencing table columns + * the constraint is associated with. + * + * @return array + */ + public function getColumns(); + + /** + * Returns the quoted representation of the column names + * the constraint is associated with. + * + * But only if they were defined with one or a column name + * is a keyword reserved by the platform. + * Otherwise the plain unquoted value as inserted is returned. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The platform to use for quotation. + * + * @return array + */ + public function getQuotedColumns(AbstractPlatform $platform); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/DB2SchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/DB2SchemaManager.php new file mode 100644 index 00000000..df6cf538 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/DB2SchemaManager.php @@ -0,0 +1,218 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * IBM Db2 Schema Manager. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Benjamin Eberlei + */ +class DB2SchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritdoc} + * + * Apparently creator is the schema not the user who created it: + * {@link http://publib.boulder.ibm.com/infocenter/dzichelp/v2r2/index.jsp?topic=/com.ibm.db29.doc.sqlref/db2z_sysibmsystablestable.htm} + */ + public function listTableNames() + { + $sql = $this->_platform->getListTablesSQL(); + $sql .= " AND CREATOR = UPPER('".$this->_conn->getUsername()."')"; + + $tables = $this->_conn->fetchAll($sql); + + return $this->_getPortableTablesList($tables); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $tableColumn = array_change_key_case($tableColumn, \CASE_LOWER); + + $length = null; + $fixed = null; + $unsigned = false; + $scale = false; + $precision = false; + + $default = null; + + if (null !== $tableColumn['default'] && 'NULL' != $tableColumn['default']) { + $default = trim($tableColumn['default'], "'"); + } + + $type = $this->_platform->getDoctrineTypeMapping($tableColumn['typename']); + + if (isset($tableColumn['comment'])) { + $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); + $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); + } + + switch (strtolower($tableColumn['typename'])) { + case 'varchar': + $length = $tableColumn['length']; + $fixed = false; + break; + case 'character': + $length = $tableColumn['length']; + $fixed = true; + break; + case 'clob': + $length = $tableColumn['length']; + break; + case 'decimal': + case 'double': + case 'real': + $scale = $tableColumn['scale']; + $precision = $tableColumn['length']; + break; + } + + $options = array( + 'length' => $length, + 'unsigned' => (bool) $unsigned, + 'fixed' => (bool) $fixed, + 'default' => $default, + 'autoincrement' => (boolean) $tableColumn['autoincrement'], + 'notnull' => (bool) ($tableColumn['nulls'] == 'N'), + 'scale' => null, + 'precision' => null, + 'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== '' + ? $tableColumn['comment'] + : null, + 'platformOptions' => array(), + ); + + if ($scale !== null && $precision !== null) { + $options['scale'] = $scale; + $options['precision'] = $precision; + } + + return new Column($tableColumn['colname'], \Doctrine\DBAL\Types\Type::getType($type), $options); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTablesList($tables) + { + $tableNames = array(); + foreach ($tables as $tableRow) { + $tableRow = array_change_key_case($tableRow, \CASE_LOWER); + $tableNames[] = $tableRow['name']; + } + + return $tableNames; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableIndexesList($tableIndexRows, $tableName = null) + { + foreach ($tableIndexRows as &$tableIndexRow) { + $tableIndexRow = array_change_key_case($tableIndexRow, \CASE_LOWER); + $tableIndexRow['primary'] = (boolean) $tableIndexRow['primary']; + } + + return parent::_getPortableTableIndexesList($tableIndexRows, $tableName); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + return new ForeignKeyConstraint( + $tableForeignKey['local_columns'], + $tableForeignKey['foreign_table'], + $tableForeignKey['foreign_columns'], + $tableForeignKey['name'], + $tableForeignKey['options'] + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $foreignKeys = array(); + + foreach ($tableForeignKeys as $tableForeignKey) { + $tableForeignKey = array_change_key_case($tableForeignKey, \CASE_LOWER); + + if (!isset($foreignKeys[$tableForeignKey['index_name']])) { + $foreignKeys[$tableForeignKey['index_name']] = array( + 'local_columns' => array($tableForeignKey['local_column']), + 'foreign_table' => $tableForeignKey['foreign_table'], + 'foreign_columns' => array($tableForeignKey['foreign_column']), + 'name' => $tableForeignKey['index_name'], + 'options' => array( + 'onUpdate' => $tableForeignKey['on_update'], + 'onDelete' => $tableForeignKey['on_delete'], + ) + ); + } else { + $foreignKeys[$tableForeignKey['index_name']]['local_columns'][] = $tableForeignKey['local_column']; + $foreignKeys[$tableForeignKey['index_name']]['foreign_columns'][] = $tableForeignKey['foreign_column']; + } + } + + return parent::_getPortableTableForeignKeysList($foreignKeys); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableForeignKeyRuleDef($def) + { + if ($def == "C") { + return "CASCADE"; + } elseif ($def == "N") { + return "SET NULL"; + } + + return null; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableViewDefinition($view) + { + $view = array_change_key_case($view, \CASE_LOWER); + // sadly this still segfaults on PDO_IBM, see http://pecl.php.net/bugs/bug.php?id=17199 + //$view['text'] = (is_resource($view['text']) ? stream_get_contents($view['text']) : $view['text']); + if (!is_resource($view['text'])) { + $pos = strpos($view['text'], ' AS '); + $sql = substr($view['text'], $pos+4); + } else { + $sql = ''; + } + + return new View($view['name'], $sql); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/DrizzleSchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/DrizzleSchemaManager.php new file mode 100644 index 00000000..e9503c35 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/DrizzleSchemaManager.php @@ -0,0 +1,119 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Types\Type; + +/** + * Schema manager for the Drizzle RDBMS. + * + * @author Kim Hemsø Rasmussen + */ +class DrizzleSchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $dbType = strtolower($tableColumn['DATA_TYPE']); + + $type = $this->_platform->getDoctrineTypeMapping($dbType); + $type = $this->extractDoctrineTypeFromComment($tableColumn['COLUMN_COMMENT'], $type); + $tableColumn['COLUMN_COMMENT'] = $this->removeDoctrineTypeFromComment($tableColumn['COLUMN_COMMENT'], $type); + + $options = array( + 'notnull' => !(bool) $tableColumn['IS_NULLABLE'], + 'length' => (int) $tableColumn['CHARACTER_MAXIMUM_LENGTH'], + 'default' => isset($tableColumn['COLUMN_DEFAULT']) ? $tableColumn['COLUMN_DEFAULT'] : null, + 'autoincrement' => (bool) $tableColumn['IS_AUTO_INCREMENT'], + 'scale' => (int) $tableColumn['NUMERIC_SCALE'], + 'precision' => (int) $tableColumn['NUMERIC_PRECISION'], + 'comment' => isset($tableColumn['COLUMN_COMMENT']) && '' !== $tableColumn['COLUMN_COMMENT'] + ? $tableColumn['COLUMN_COMMENT'] + : null, + ); + + $column = new Column($tableColumn['COLUMN_NAME'], Type::getType($type), $options); + + if ( ! empty($tableColumn['COLLATION_NAME'])) { + $column->setPlatformOption('collation', $tableColumn['COLLATION_NAME']); + } + + return $column; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database['SCHEMA_NAME']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableDefinition($table) + { + return $table['TABLE_NAME']; + } + + /** + * {@inheritdoc} + */ + public function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + $columns = array(); + foreach (explode(',', $tableForeignKey['CONSTRAINT_COLUMNS']) as $value) { + $columns[] = trim($value, ' `'); + } + + $refColumns = array(); + foreach (explode(',', $tableForeignKey['REFERENCED_TABLE_COLUMNS']) as $value) { + $refColumns[] = trim($value, ' `'); + } + + return new ForeignKeyConstraint( + $columns, + $tableForeignKey['REFERENCED_TABLE_NAME'], + $refColumns, + $tableForeignKey['CONSTRAINT_NAME'], + array( + 'onUpdate' => $tableForeignKey['UPDATE_RULE'], + 'onDelete' => $tableForeignKey['DELETE_RULE'], + ) + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) + { + $indexes = array(); + foreach ($tableIndexes as $k) { + $k['primary'] = (boolean) $k['primary']; + $indexes[] = $k; + } + + return parent::_getPortableTableIndexesList($indexes, $tableName); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/ForeignKeyConstraint.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/ForeignKeyConstraint.php new file mode 100644 index 00000000..6070eb2e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/ForeignKeyConstraint.php @@ -0,0 +1,389 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * An abstraction class for a foreign key constraint. + * + * @author Benjamin Eberlei + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.0 + */ +class ForeignKeyConstraint extends AbstractAsset implements Constraint +{ + /** + * Instance of the referencing table the foreign key constraint is associated with. + * + * @var \Doctrine\DBAL\Schema\Table + */ + protected $_localTable; + + /** + * Asset identifier instances of the referencing table column names the foreign key constraint is associated with. + * array($columnName => Identifier) + * + * @var Identifier[] + */ + protected $_localColumnNames; + + /** + * Table or asset identifier instance of the referenced table name the foreign key constraint is associated with. + * + * @var Table|Identifier + */ + protected $_foreignTableName; + + /** + * Asset identifier instances of the referenced table column names the foreign key constraint is associated with. + * array($columnName => Identifier) + * + * @var Identifier[] + */ + protected $_foreignColumnNames; + + /** + * @var array Options associated with the foreign key constraint. + */ + protected $_options; + + /** + * Initializes the foreign key constraint. + * + * @param array $localColumnNames Names of the referencing table columns. + * @param Table|string $foreignTableName Referenced table. + * @param array $foreignColumnNames Names of the referenced table columns. + * @param string|null $name Name of the foreign key constraint. + * @param array $options Options associated with the foreign key constraint. + */ + public function __construct(array $localColumnNames, $foreignTableName, array $foreignColumnNames, $name = null, array $options = array()) + { + $this->_setName($name); + $identifierConstructorCallback = function ($column) { + return new Identifier($column); + }; + $this->_localColumnNames = $localColumnNames + ? array_combine($localColumnNames, array_map($identifierConstructorCallback, $localColumnNames)) + : array(); + + if ($foreignTableName instanceof Table) { + $this->_foreignTableName = $foreignTableName; + } else { + $this->_foreignTableName = new Identifier($foreignTableName); + } + + $this->_foreignColumnNames = $foreignColumnNames + ? array_combine($foreignColumnNames, array_map($identifierConstructorCallback, $foreignColumnNames)) + : array(); + $this->_options = $options; + } + + /** + * Returns the name of the referencing table + * the foreign key constraint is associated with. + * + * @return string + */ + public function getLocalTableName() + { + return $this->_localTable->getName(); + } + + /** + * Sets the Table instance of the referencing table + * the foreign key constraint is associated with. + * + * @param \Doctrine\DBAL\Schema\Table $table Instance of the referencing table. + * + * @return void + */ + public function setLocalTable(Table $table) + { + $this->_localTable = $table; + } + + /** + * @return Table + */ + public function getLocalTable() + { + return $this->_localTable; + } + + /** + * Returns the names of the referencing table columns + * the foreign key constraint is associated with. + * + * @return array + */ + public function getLocalColumns() + { + return array_keys($this->_localColumnNames); + } + + /** + * Returns the quoted representation of the referencing table column names + * the foreign key constraint is associated with. + * + * But only if they were defined with one or the referencing table column name + * is a keyword reserved by the platform. + * Otherwise the plain unquoted value as inserted is returned. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The platform to use for quotation. + * + * @return array + */ + public function getQuotedLocalColumns(AbstractPlatform $platform) + { + $columns = array(); + + foreach ($this->_localColumnNames as $column) { + $columns[] = $column->getQuotedName($platform); + } + + return $columns; + } + + /** + * Returns unquoted representation of local table column names for comparison with other FK + * + * @return array + */ + public function getUnquotedLocalColumns() + { + return array_map(array($this, 'trimQuotes'), $this->getLocalColumns()); + } + + /** + * Returns unquoted representation of foreign table column names for comparison with other FK + * + * @return array + */ + public function getUnquotedForeignColumns() + { + return array_map(array($this, 'trimQuotes'), $this->getForeignColumns()); + } + + /** + * {@inheritdoc} + * + * @see getLocalColumns + */ + public function getColumns() + { + return $this->getLocalColumns(); + } + + /** + * Returns the quoted representation of the referencing table column names + * the foreign key constraint is associated with. + * + * But only if they were defined with one or the referencing table column name + * is a keyword reserved by the platform. + * Otherwise the plain unquoted value as inserted is returned. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The platform to use for quotation. + * + * @see getQuotedLocalColumns + * + * @return array + */ + public function getQuotedColumns(AbstractPlatform $platform) + { + return $this->getQuotedLocalColumns($platform); + } + + /** + * Returns the name of the referenced table + * the foreign key constraint is associated with. + * + * @return string + */ + public function getForeignTableName() + { + return $this->_foreignTableName->getName(); + } + + /** + * Returns the non-schema qualified foreign table name. + * + * @return string + */ + public function getUnqualifiedForeignTableName() + { + $parts = explode(".", $this->_foreignTableName->getName()); + + return strtolower(end($parts)); + } + + /** + * Returns the quoted representation of the referenced table name + * the foreign key constraint is associated with. + * + * But only if it was defined with one or the referenced table name + * is a keyword reserved by the platform. + * Otherwise the plain unquoted value as inserted is returned. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The platform to use for quotation. + * + * @return string + */ + public function getQuotedForeignTableName(AbstractPlatform $platform) + { + return $this->_foreignTableName->getQuotedName($platform); + } + + /** + * Returns the names of the referenced table columns + * the foreign key constraint is associated with. + * + * @return array + */ + public function getForeignColumns() + { + return array_keys($this->_foreignColumnNames); + } + + /** + * Returns the quoted representation of the referenced table column names + * the foreign key constraint is associated with. + * + * But only if they were defined with one or the referenced table column name + * is a keyword reserved by the platform. + * Otherwise the plain unquoted value as inserted is returned. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The platform to use for quotation. + * + * @return array + */ + public function getQuotedForeignColumns(AbstractPlatform $platform) + { + $columns = array(); + + foreach ($this->_foreignColumnNames as $column) { + $columns[] = $column->getQuotedName($platform); + } + + return $columns; + } + + /** + * Returns whether or not a given option + * is associated with the foreign key constraint. + * + * @param string $name Name of the option to check. + * + * @return boolean + */ + public function hasOption($name) + { + return isset($this->_options[$name]); + } + + /** + * Returns an option associated with the foreign key constraint. + * + * @param string $name Name of the option the foreign key constraint is associated with. + * + * @return mixed + */ + public function getOption($name) + { + return $this->_options[$name]; + } + + /** + * Returns the options associated with the foreign key constraint. + * + * @return array + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Returns the referential action for UPDATE operations + * on the referenced table the foreign key constraint is associated with. + * + * @return string|null + */ + public function onUpdate() + { + return $this->onEvent('onUpdate'); + } + + /** + * Returns the referential action for DELETE operations + * on the referenced table the foreign key constraint is associated with. + * + * @return string|null + */ + public function onDelete() + { + return $this->onEvent('onDelete'); + } + + /** + * Returns the referential action for a given database operation + * on the referenced table the foreign key constraint is associated with. + * + * @param string $event Name of the database operation/event to return the referential action for. + * + * @return string|null + */ + private function onEvent($event) + { + if (isset($this->_options[$event])) { + $onEvent = strtoupper($this->_options[$event]); + + if ( ! in_array($onEvent, array('NO ACTION', 'RESTRICT'))) { + return $onEvent; + } + } + + return false; + } + + /** + * Checks whether this foreign key constraint intersects the given index columns. + * + * Returns `true` if at least one of this foreign key's local columns + * matches one of the given index's columns, `false` otherwise. + * + * @param Index $index The index to be checked against. + * + * @return boolean + */ + public function intersectsIndexColumns(Index $index) + { + foreach ($index->getColumns() as $indexColumn) { + foreach ($this->_localColumnNames as $localColumn) { + if (strtolower($indexColumn) === strtolower($localColumn->getName())) { + return true; + } + } + } + + return false; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Identifier.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Identifier.php new file mode 100644 index 00000000..babe583e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Identifier.php @@ -0,0 +1,48 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * An abstraction class for an asset identifier. + * + * Wraps identifier names like column names in indexes / foreign keys + * in an abstract class for proper quotation capabilities. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.4 + */ +class Identifier extends AbstractAsset +{ + /** + * Constructor. + * + * @param string $identifier Identifier name to wrap. + * @param bool $quote Whether to force quoting the given identifier. + */ + public function __construct($identifier, $quote = false) + { + $this->_setName($identifier); + + if ($quote && ! $this->_quoted) { + $this->_setName('"' . $this->getName() . '"'); + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Index.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Index.php new file mode 100644 index 00000000..bc5cd93d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Index.php @@ -0,0 +1,358 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +class Index extends AbstractAsset implements Constraint +{ + /** + * Asset identifier instances of the column names the index is associated with. + * array($columnName => Identifier) + * + * @var Identifier[] + */ + protected $_columns = array(); + + /** + * @var boolean + */ + protected $_isUnique = false; + + /** + * @var boolean + */ + protected $_isPrimary = false; + + /** + * Platform specific flags for indexes. + * array($flagName => true) + * + * @var array + */ + protected $_flags = array(); + + /** + * Platform specific options + * + * @todo $_flags should eventually be refactored into options + * + * @var array + */ + private $options = array(); + + /** + * @param string $indexName + * @param string[] $columns + * @param boolean $isUnique + * @param boolean $isPrimary + * @param string[] $flags + * @param array $options + */ + public function __construct($indexName, array $columns, $isUnique = false, $isPrimary = false, array $flags = array(), array $options = array()) + { + $isUnique = $isUnique || $isPrimary; + + $this->_setName($indexName); + $this->_isUnique = $isUnique; + $this->_isPrimary = $isPrimary; + $this->options = $options; + + foreach ($columns as $column) { + $this->_addColumn($column); + } + foreach ($flags as $flag) { + $this->addFlag($flag); + } + } + + /** + * @param string $column + * + * @return void + * + * @throws \InvalidArgumentException + */ + protected function _addColumn($column) + { + if (is_string($column)) { + $this->_columns[$column] = new Identifier($column); + } else { + throw new \InvalidArgumentException("Expecting a string as Index Column"); + } + } + + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array_keys($this->_columns); + } + + /** + * {@inheritdoc} + */ + public function getQuotedColumns(AbstractPlatform $platform) + { + $columns = array(); + + foreach ($this->_columns as $column) { + $columns[] = $column->getQuotedName($platform); + } + + return $columns; + } + + /** + * @return string[] + */ + public function getUnquotedColumns() + { + return array_map(array($this, 'trimQuotes'), $this->getColumns()); + } + + /** + * Is the index neither unique nor primary key? + * + * @return boolean + */ + public function isSimpleIndex() + { + return !$this->_isPrimary && !$this->_isUnique; + } + + /** + * @return boolean + */ + public function isUnique() + { + return $this->_isUnique; + } + + /** + * @return boolean + */ + public function isPrimary() + { + return $this->_isPrimary; + } + + /** + * @param string $columnName + * @param integer $pos + * + * @return boolean + */ + public function hasColumnAtPosition($columnName, $pos = 0) + { + $columnName = $this->trimQuotes(strtolower($columnName)); + $indexColumns = array_map('strtolower', $this->getUnquotedColumns()); + + return array_search($columnName, $indexColumns) === $pos; + } + + /** + * Checks if this index exactly spans the given column names in the correct order. + * + * @param array $columnNames + * + * @return boolean + */ + public function spansColumns(array $columnNames) + { + $columns = $this->getColumns(); + $numberOfColumns = count($columns); + $sameColumns = true; + + for ($i = 0; $i < $numberOfColumns; $i++) { + if ( ! isset($columnNames[$i]) || $this->trimQuotes(strtolower($columns[$i])) !== $this->trimQuotes(strtolower($columnNames[$i]))) { + $sameColumns = false; + } + } + + return $sameColumns; + } + + /** + * Checks if the other index already fulfills all the indexing and constraint needs of the current one. + * + * @param Index $other + * + * @return boolean + */ + public function isFullfilledBy(Index $other) + { + // allow the other index to be equally large only. It being larger is an option + // but it creates a problem with scenarios of the kind PRIMARY KEY(foo,bar) UNIQUE(foo) + if (count($other->getColumns()) != count($this->getColumns())) { + return false; + } + + // Check if columns are the same, and even in the same order + $sameColumns = $this->spansColumns($other->getColumns()); + + if ($sameColumns) { + if ( ! $this->samePartialIndex($other)) { + return false; + } + + if ( ! $this->isUnique() && ! $this->isPrimary()) { + // this is a special case: If the current key is neither primary or unique, any uniqe or + // primary key will always have the same effect for the index and there cannot be any constraint + // overlaps. This means a primary or unique index can always fulfill the requirements of just an + // index that has no constraints. + return true; + } + + if ($other->isPrimary() != $this->isPrimary()) { + return false; + } + + if ($other->isUnique() != $this->isUnique()) { + return false; + } + + return true; + } + + return false; + } + + /** + * Detects if the other index is a non-unique, non primary index that can be overwritten by this one. + * + * @param Index $other + * + * @return boolean + */ + public function overrules(Index $other) + { + if ($other->isPrimary()) { + return false; + } elseif ($this->isSimpleIndex() && $other->isUnique()) { + return false; + } + + if ($this->spansColumns($other->getColumns()) && ($this->isPrimary() || $this->isUnique()) && $this->samePartialIndex($other)) { + return true; + } + + return false; + } + + /** + * Returns platform specific flags for indexes. + * + * @return string[] + */ + public function getFlags() + { + return array_keys($this->_flags); + } + + /** + * Adds Flag for an index that translates to platform specific handling. + * + * @example $index->addFlag('CLUSTERED') + * + * @param string $flag + * + * @return Index + */ + public function addFlag($flag) + { + $this->_flags[strtolower($flag)] = true; + + return $this; + } + + /** + * Does this index have a specific flag? + * + * @param string $flag + * + * @return boolean + */ + public function hasFlag($flag) + { + return isset($this->_flags[strtolower($flag)]); + } + + /** + * Removes a flag. + * + * @param string $flag + * + * @return void + */ + public function removeFlag($flag) + { + unset($this->_flags[strtolower($flag)]); + } + + /** + * @param string $name + * + * @return boolean + */ + public function hasOption($name) + { + return isset($this->options[strtolower($name)]); + } + + /** + * @param string $name + * + * @return mixed + */ + public function getOption($name) + { + return $this->options[strtolower($name)]; + } + + /** + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * Return whether the two indexes have the same partial index + * @param \Doctrine\DBAL\Schema\Index $other + * + * @return boolean + */ + private function samePartialIndex(Index $other) + { + if ($this->hasOption('where') && $other->hasOption('where') && $this->getOption('where') == $other->getOption('where')) { + return true; + } + + if ( ! $this->hasOption('where') && ! $other->hasOption('where')) { + return true; + } + + return false; + } + +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php new file mode 100644 index 00000000..0b36762b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php @@ -0,0 +1,252 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Types\Type; + +/** + * Schema manager for the MySql RDBMS. + * + * @author Konsta Vesterinen + * @author Lukas Smith (PEAR MDB2 library) + * @author Roman Borschel + * @author Benjamin Eberlei + * @since 2.0 + */ +class MySqlSchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritdoc} + */ + protected function _getPortableViewDefinition($view) + { + return new View($view['TABLE_NAME'], $view['VIEW_DEFINITION']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableDefinition($table) + { + return array_shift($table); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableUserDefinition($user) + { + return array( + 'user' => $user['User'], + 'password' => $user['Password'], + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) + { + foreach ($tableIndexes as $k => $v) { + $v = array_change_key_case($v, CASE_LOWER); + if ($v['key_name'] == 'PRIMARY') { + $v['primary'] = true; + } else { + $v['primary'] = false; + } + if (strpos($v['index_type'], 'FULLTEXT') !== false) { + $v['flags'] = array('FULLTEXT'); + } elseif (strpos($v['index_type'], 'SPATIAL') !== false) { + $v['flags'] = array('SPATIAL'); + } + $tableIndexes[$k] = $v; + } + + return parent::_getPortableTableIndexesList($tableIndexes, $tableName); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableSequenceDefinition($sequence) + { + return end($sequence); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database['Database']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); + + $dbType = strtolower($tableColumn['type']); + $dbType = strtok($dbType, '(), '); + if (isset($tableColumn['length'])) { + $length = $tableColumn['length']; + } else { + $length = strtok('(), '); + } + + $fixed = null; + + if ( ! isset($tableColumn['name'])) { + $tableColumn['name'] = ''; + } + + $scale = null; + $precision = null; + + $type = $this->_platform->getDoctrineTypeMapping($dbType); + + // In cases where not connected to a database DESCRIBE $table does not return 'Comment' + if (isset($tableColumn['comment'])) { + $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); + $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); + } + + switch ($dbType) { + case 'char': + case 'binary': + $fixed = true; + break; + case 'float': + case 'double': + case 'real': + case 'numeric': + case 'decimal': + if (preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['type'], $match)) { + $precision = $match[1]; + $scale = $match[2]; + $length = null; + } + break; + case 'tinytext': + $length = MySqlPlatform::LENGTH_LIMIT_TINYTEXT; + break; + case 'text': + $length = MySqlPlatform::LENGTH_LIMIT_TEXT; + break; + case 'mediumtext': + $length = MySqlPlatform::LENGTH_LIMIT_MEDIUMTEXT; + break; + case 'tinyblob': + $length = MySqlPlatform::LENGTH_LIMIT_TINYBLOB; + break; + case 'blob': + $length = MySqlPlatform::LENGTH_LIMIT_BLOB; + break; + case 'mediumblob': + $length = MySqlPlatform::LENGTH_LIMIT_MEDIUMBLOB; + break; + case 'tinyint': + case 'smallint': + case 'mediumint': + case 'int': + case 'integer': + case 'bigint': + case 'year': + $length = null; + break; + } + + $length = ((int) $length == 0) ? null : (int) $length; + + $options = array( + 'length' => $length, + 'unsigned' => (bool) (strpos($tableColumn['type'], 'unsigned') !== false), + 'fixed' => (bool) $fixed, + 'default' => isset($tableColumn['default']) ? $tableColumn['default'] : null, + 'notnull' => (bool) ($tableColumn['null'] != 'YES'), + 'scale' => null, + 'precision' => null, + 'autoincrement' => (bool) (strpos($tableColumn['extra'], 'auto_increment') !== false), + 'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== '' + ? $tableColumn['comment'] + : null, + ); + + if ($scale !== null && $precision !== null) { + $options['scale'] = $scale; + $options['precision'] = $precision; + } + + $column = new Column($tableColumn['field'], Type::getType($type), $options); + + if (isset($tableColumn['collation'])) { + $column->setPlatformOption('collation', $tableColumn['collation']); + } + + return $column; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $list = array(); + foreach ($tableForeignKeys as $value) { + $value = array_change_key_case($value, CASE_LOWER); + if (!isset($list[$value['constraint_name']])) { + if (!isset($value['delete_rule']) || $value['delete_rule'] == "RESTRICT") { + $value['delete_rule'] = null; + } + if (!isset($value['update_rule']) || $value['update_rule'] == "RESTRICT") { + $value['update_rule'] = null; + } + + $list[$value['constraint_name']] = array( + 'name' => $value['constraint_name'], + 'local' => array(), + 'foreign' => array(), + 'foreignTable' => $value['referenced_table_name'], + 'onDelete' => $value['delete_rule'], + 'onUpdate' => $value['update_rule'], + ); + } + $list[$value['constraint_name']]['local'][] = $value['column_name']; + $list[$value['constraint_name']]['foreign'][] = $value['referenced_column_name']; + } + + $result = array(); + foreach ($list as $constraint) { + $result[] = new ForeignKeyConstraint( + array_values($constraint['local']), $constraint['foreignTable'], + array_values($constraint['foreign']), $constraint['name'], + array( + 'onDelete' => $constraint['onDelete'], + 'onUpdate' => $constraint['onUpdate'], + ) + ); + } + + return $result; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php new file mode 100644 index 00000000..9feecdc7 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php @@ -0,0 +1,425 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver\DriverException; +use Doctrine\DBAL\Types\Type; + +/** + * Oracle Schema Manager. + * + * @author Konsta Vesterinen + * @author Lukas Smith (PEAR MDB2 library) + * @author Benjamin Eberlei + * @since 2.0 + */ +class OracleSchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritdoc} + */ + public function dropDatabase($database) + { + try { + parent::dropDatabase($database); + } catch (DBALException $exception) { + $exception = $exception->getPrevious(); + + if (! $exception instanceof DriverException) { + throw $exception; + } + + // If we have a error code 1940 (ORA-01940), the drop database operation failed + // because of active connections on the database. + // To force dropping the database, we first have to close all active connections + // on that database and issue the drop database operation again. + if ($exception->getErrorCode() !== 1940) { + throw $exception; + } + + $this->killUserSessions($database); + + parent::dropDatabase($database); + } + } + + /** + * {@inheritdoc} + */ + protected function _getPortableViewDefinition($view) + { + $view = \array_change_key_case($view, CASE_LOWER); + + return new View($this->getQuotedIdentifierName($view['view_name']), $view['text']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableUserDefinition($user) + { + $user = \array_change_key_case($user, CASE_LOWER); + + return array( + 'user' => $user['username'], + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableDefinition($table) + { + $table = \array_change_key_case($table, CASE_LOWER); + + return $this->getQuotedIdentifierName($table['table_name']); + } + + /** + * {@inheritdoc} + * + * @license New BSD License + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) + { + $indexBuffer = array(); + foreach ($tableIndexes as $tableIndex) { + $tableIndex = \array_change_key_case($tableIndex, CASE_LOWER); + + $keyName = strtolower($tableIndex['name']); + + if (strtolower($tableIndex['is_primary']) == "p") { + $keyName = 'primary'; + $buffer['primary'] = true; + $buffer['non_unique'] = false; + } else { + $buffer['primary'] = false; + $buffer['non_unique'] = ($tableIndex['is_unique'] == 0) ? true : false; + } + $buffer['key_name'] = $keyName; + $buffer['column_name'] = $this->getQuotedIdentifierName($tableIndex['column_name']); + $indexBuffer[] = $buffer; + } + + return parent::_getPortableTableIndexesList($indexBuffer, $tableName); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $tableColumn = \array_change_key_case($tableColumn, CASE_LOWER); + + $dbType = strtolower($tableColumn['data_type']); + if (strpos($dbType, "timestamp(") === 0) { + if (strpos($dbType, "with time zone")) { + $dbType = "timestamptz"; + } else { + $dbType = "timestamp"; + } + } + + $unsigned = $fixed = null; + + if ( ! isset($tableColumn['column_name'])) { + $tableColumn['column_name'] = ''; + } + + // Default values returned from database sometimes have trailing spaces. + $tableColumn['data_default'] = trim($tableColumn['data_default']); + + if ($tableColumn['data_default'] === '' || $tableColumn['data_default'] === 'NULL') { + $tableColumn['data_default'] = null; + } + + if (null !== $tableColumn['data_default']) { + // Default values returned from database are enclosed in single quotes. + $tableColumn['data_default'] = trim($tableColumn['data_default'], "'"); + } + + $precision = null; + $scale = null; + + $type = $this->_platform->getDoctrineTypeMapping($dbType); + $type = $this->extractDoctrineTypeFromComment($tableColumn['comments'], $type); + $tableColumn['comments'] = $this->removeDoctrineTypeFromComment($tableColumn['comments'], $type); + + switch ($dbType) { + case 'number': + if ($tableColumn['data_precision'] == 20 && $tableColumn['data_scale'] == 0) { + $precision = 20; + $scale = 0; + $type = 'bigint'; + } elseif ($tableColumn['data_precision'] == 5 && $tableColumn['data_scale'] == 0) { + $type = 'smallint'; + $precision = 5; + $scale = 0; + } elseif ($tableColumn['data_precision'] == 1 && $tableColumn['data_scale'] == 0) { + $precision = 1; + $scale = 0; + $type = 'boolean'; + } elseif ($tableColumn['data_scale'] > 0) { + $precision = $tableColumn['data_precision']; + $scale = $tableColumn['data_scale']; + $type = 'decimal'; + } + $length = null; + break; + case 'pls_integer': + case 'binary_integer': + $length = null; + break; + case 'varchar': + case 'varchar2': + case 'nvarchar2': + $length = $tableColumn['char_length']; + $fixed = false; + break; + case 'char': + case 'nchar': + $length = $tableColumn['char_length']; + $fixed = true; + break; + case 'date': + case 'timestamp': + $length = null; + break; + case 'float': + case 'binary_float': + case 'binary_double': + $precision = $tableColumn['data_precision']; + $scale = $tableColumn['data_scale']; + $length = null; + break; + case 'clob': + case 'nclob': + $length = null; + break; + case 'blob': + case 'raw': + case 'long raw': + case 'bfile': + $length = null; + break; + case 'rowid': + case 'urowid': + default: + $length = null; + } + + $options = array( + 'notnull' => (bool) ($tableColumn['nullable'] === 'N'), + 'fixed' => (bool) $fixed, + 'unsigned' => (bool) $unsigned, + 'default' => $tableColumn['data_default'], + 'length' => $length, + 'precision' => $precision, + 'scale' => $scale, + 'comment' => isset($tableColumn['comments']) && '' !== $tableColumn['comments'] + ? $tableColumn['comments'] + : null, + 'platformDetails' => array(), + ); + + return new Column($this->getQuotedIdentifierName($tableColumn['column_name']), Type::getType($type), $options); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $list = array(); + foreach ($tableForeignKeys as $value) { + $value = \array_change_key_case($value, CASE_LOWER); + if (!isset($list[$value['constraint_name']])) { + if ($value['delete_rule'] == "NO ACTION") { + $value['delete_rule'] = null; + } + + $list[$value['constraint_name']] = array( + 'name' => $this->getQuotedIdentifierName($value['constraint_name']), + 'local' => array(), + 'foreign' => array(), + 'foreignTable' => $value['references_table'], + 'onDelete' => $value['delete_rule'], + ); + } + + $localColumn = $this->getQuotedIdentifierName($value['local_column']); + $foreignColumn = $this->getQuotedIdentifierName($value['foreign_column']); + + $list[$value['constraint_name']]['local'][$value['position']] = $localColumn; + $list[$value['constraint_name']]['foreign'][$value['position']] = $foreignColumn; + } + + $result = array(); + foreach ($list as $constraint) { + $result[] = new ForeignKeyConstraint( + array_values($constraint['local']), $this->getQuotedIdentifierName($constraint['foreignTable']), + array_values($constraint['foreign']), $this->getQuotedIdentifierName($constraint['name']), + array('onDelete' => $constraint['onDelete']) + ); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableSequenceDefinition($sequence) + { + $sequence = \array_change_key_case($sequence, CASE_LOWER); + + return new Sequence( + $this->getQuotedIdentifierName($sequence['sequence_name']), + $sequence['increment_by'], + $sequence['min_value'] + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableFunctionDefinition($function) + { + $function = \array_change_key_case($function, CASE_LOWER); + + return $function['name']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + $database = \array_change_key_case($database, CASE_LOWER); + + return $database['username']; + } + + /** + * {@inheritdoc} + */ + public function createDatabase($database = null) + { + if (is_null($database)) { + $database = $this->_conn->getDatabase(); + } + + $params = $this->_conn->getParams(); + $username = $database; + $password = $params['password']; + + $query = 'CREATE USER ' . $username . ' IDENTIFIED BY ' . $password; + $this->_conn->executeUpdate($query); + + $query = 'GRANT DBA TO ' . $username; + $this->_conn->executeUpdate($query); + + return true; + } + + /** + * @param string $table + * + * @return boolean + */ + public function dropAutoincrement($table) + { + $sql = $this->_platform->getDropAutoincrementSql($table); + foreach ($sql as $query) { + $this->_conn->executeUpdate($query); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function dropTable($name) + { + $this->tryMethod('dropAutoincrement', $name); + + parent::dropTable($name); + } + + /** + * Returns the quoted representation of the given identifier name. + * + * Quotes non-uppercase identifiers explicitly to preserve case + * and thus make references to the particular identifier work. + * + * @param string $identifier The identifier to quote. + * + * @return string The quoted identifier. + */ + private function getQuotedIdentifierName($identifier) + { + if (preg_match('/[a-z]/', $identifier)) { + return $this->_platform->quoteIdentifier($identifier); + } + + return $identifier; + } + + /** + * Kills sessions connected with the given user. + * + * This is useful to force DROP USER operations which could fail because of active user sessions. + * + * @param string $user The name of the user to kill sessions for. + * + * @return void + */ + private function killUserSessions($user) + { + $sql = <<_conn->fetchAll($sql, array(strtoupper($user))); + + foreach ($activeUserSessions as $activeUserSession) { + $activeUserSession = array_change_key_case($activeUserSession, \CASE_LOWER); + + $this->_execSql( + sprintf( + "ALTER SYSTEM KILL SESSION '%s, %s' IMMEDIATE", + $activeUserSession['sid'], + $activeUserSession['serial#'] + ) + ); + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php new file mode 100644 index 00000000..5b7b82e1 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php @@ -0,0 +1,438 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Exception\DriverException; +use Doctrine\DBAL\Types\Type; + +/** + * PostgreSQL Schema Manager. + * + * @author Konsta Vesterinen + * @author Lukas Smith (PEAR MDB2 library) + * @author Benjamin Eberlei + * @since 2.0 + */ +class PostgreSqlSchemaManager extends AbstractSchemaManager +{ + /** + * @var array + */ + private $existingSchemaPaths; + + /** + * Gets all the existing schema names. + * + * @return array + */ + public function getSchemaNames() + { + $rows = $this->_conn->fetchAll("SELECT nspname as schema_name FROM pg_namespace WHERE nspname !~ '^pg_.*' and nspname != 'information_schema'"); + + return array_map(function ($v) { return $v['schema_name']; }, $rows); + } + + /** + * Returns an array of schema search paths. + * + * This is a PostgreSQL only function. + * + * @return array + */ + public function getSchemaSearchPaths() + { + $params = $this->_conn->getParams(); + $schema = explode(",", $this->_conn->fetchColumn('SHOW search_path')); + + if (isset($params['user'])) { + $schema = str_replace('"$user"', $params['user'], $schema); + } + + return array_map('trim', $schema); + } + + /** + * Gets names of all existing schemas in the current users search path. + * + * This is a PostgreSQL only function. + * + * @return array + */ + public function getExistingSchemaSearchPaths() + { + if ($this->existingSchemaPaths === null) { + $this->determineExistingSchemaSearchPaths(); + } + + return $this->existingSchemaPaths; + } + + /** + * Sets or resets the order of the existing schemas in the current search path of the user. + * + * This is a PostgreSQL only function. + * + * @return void + */ + public function determineExistingSchemaSearchPaths() + { + $names = $this->getSchemaNames(); + $paths = $this->getSchemaSearchPaths(); + + $this->existingSchemaPaths = array_filter($paths, function ($v) use ($names) { + return in_array($v, $names); + }); + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($database) + { + try { + parent::dropDatabase($database); + } catch (DriverException $exception) { + // If we have a SQLSTATE 55006, the drop database operation failed + // because of active connections on the database. + // To force dropping the database, we first have to close all active connections + // on that database and issue the drop database operation again. + if ($exception->getSQLState() !== '55006') { + throw $exception; + } + + $this->_execSql( + array( + $this->_platform->getDisallowDatabaseConnectionsSQL($database), + $this->_platform->getCloseActiveDatabaseConnectionsSQL($database), + ) + ); + + parent::dropDatabase($database); + } + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + $onUpdate = null; + $onDelete = null; + + if (preg_match('(ON UPDATE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) { + $onUpdate = $match[1]; + } + if (preg_match('(ON DELETE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) { + $onDelete = $match[1]; + } + + if (preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values)) { + // PostgreSQL returns identifiers that are keywords with quotes, we need them later, don't get + // the idea to trim them here. + $localColumns = array_map('trim', explode(",", $values[1])); + $foreignColumns = array_map('trim', explode(",", $values[3])); + $foreignTable = $values[2]; + } + + return new ForeignKeyConstraint( + $localColumns, $foreignTable, $foreignColumns, $tableForeignKey['conname'], + array('onUpdate' => $onUpdate, 'onDelete' => $onDelete) + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTriggerDefinition($trigger) + { + return $trigger['trigger_name']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableViewDefinition($view) + { + return new View($view['schemaname'].'.'.$view['viewname'], $view['definition']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableUserDefinition($user) + { + return array( + 'user' => $user['usename'], + 'password' => $user['passwd'] + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableDefinition($table) + { + $schemas = $this->getExistingSchemaSearchPaths(); + $firstSchema = array_shift($schemas); + + if ($table['schema_name'] == $firstSchema) { + return $table['table_name']; + } else { + return $table['schema_name'] . "." . $table['table_name']; + } + } + + /** + * {@inheritdoc} + * + * @license New BSD License + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) + { + $buffer = array(); + foreach ($tableIndexes as $row) { + $colNumbers = explode(' ', $row['indkey']); + $colNumbersSql = 'IN (' . join(' ,', $colNumbers) . ' )'; + $columnNameSql = "SELECT attnum, attname FROM pg_attribute + WHERE attrelid={$row['indrelid']} AND attnum $colNumbersSql ORDER BY attnum ASC;"; + + $stmt = $this->_conn->executeQuery($columnNameSql); + $indexColumns = $stmt->fetchAll(); + + // required for getting the order of the columns right. + foreach ($colNumbers as $colNum) { + foreach ($indexColumns as $colRow) { + if ($colNum == $colRow['attnum']) { + $buffer[] = array( + 'key_name' => $row['relname'], + 'column_name' => trim($colRow['attname']), + 'non_unique' => !$row['indisunique'], + 'primary' => $row['indisprimary'], + 'where' => $row['where'], + ); + } + } + } + } + + return parent::_getPortableTableIndexesList($buffer, $tableName); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database['datname']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableSequencesList($sequences) + { + $sequenceDefinitions = array(); + + foreach ($sequences as $sequence) { + if ($sequence['schemaname'] != 'public') { + $sequenceName = $sequence['schemaname'] . "." . $sequence['relname']; + } else { + $sequenceName = $sequence['relname']; + } + + $sequenceDefinitions[$sequenceName] = $sequence; + } + + $list = array(); + + foreach ($this->filterAssetNames(array_keys($sequenceDefinitions)) as $sequenceName) { + $list[] = $this->_getPortableSequenceDefinition($sequenceDefinitions[$sequenceName]); + } + + return $list; + } + + /** + * {@inheritdoc} + */ + protected function getPortableNamespaceDefinition(array $namespace) + { + return $namespace['nspname']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableSequenceDefinition($sequence) + { + if ($sequence['schemaname'] != 'public') { + $sequenceName = $sequence['schemaname'] . "." . $sequence['relname']; + } else { + $sequenceName = $sequence['relname']; + } + + $data = $this->_conn->fetchAll('SELECT min_value, increment_by FROM ' . $this->_platform->quoteIdentifier($sequenceName)); + + return new Sequence($sequenceName, $data[0]['increment_by'], $data[0]['min_value']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); + + if (strtolower($tableColumn['type']) === 'varchar' || strtolower($tableColumn['type']) === 'bpchar') { + // get length from varchar definition + $length = preg_replace('~.*\(([0-9]*)\).*~', '$1', $tableColumn['complete_type']); + $tableColumn['length'] = $length; + } + + $matches = array(); + + $autoincrement = false; + if (preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches)) { + $tableColumn['sequence'] = $matches[1]; + $tableColumn['default'] = null; + $autoincrement = true; + } + + if (preg_match("/^'(.*)'::.*$/", $tableColumn['default'], $matches)) { + $tableColumn['default'] = $matches[1]; + } + + if (stripos($tableColumn['default'], 'NULL') === 0) { + $tableColumn['default'] = null; + } + + $length = (isset($tableColumn['length'])) ? $tableColumn['length'] : null; + if ($length == '-1' && isset($tableColumn['atttypmod'])) { + $length = $tableColumn['atttypmod'] - 4; + } + if ((int) $length <= 0) { + $length = null; + } + $fixed = null; + + if (!isset($tableColumn['name'])) { + $tableColumn['name'] = ''; + } + + $precision = null; + $scale = null; + + $dbType = strtolower($tableColumn['type']); + if (strlen($tableColumn['domain_type']) && !$this->_platform->hasDoctrineTypeMappingFor($tableColumn['type'])) { + $dbType = strtolower($tableColumn['domain_type']); + $tableColumn['complete_type'] = $tableColumn['domain_complete_type']; + } + + $type = $this->_platform->getDoctrineTypeMapping($dbType); + $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); + $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); + + switch ($dbType) { + case 'smallint': + case 'int2': + $length = null; + break; + case 'int': + case 'int4': + case 'integer': + $length = null; + break; + case 'bigint': + case 'int8': + $length = null; + break; + case 'bool': + case 'boolean': + if ($tableColumn['default'] === 'true') { + $tableColumn['default'] = true; + } + + if ($tableColumn['default'] === 'false') { + $tableColumn['default'] = false; + } + + $length = null; + break; + case 'text': + $fixed = false; + break; + case 'varchar': + case 'interval': + case '_varchar': + $fixed = false; + break; + case 'char': + case 'bpchar': + $fixed = true; + break; + case 'float': + case 'float4': + case 'float8': + case 'double': + case 'double precision': + case 'real': + case 'decimal': + case 'money': + case 'numeric': + if (preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['complete_type'], $match)) { + $precision = $match[1]; + $scale = $match[2]; + $length = null; + } + break; + case 'year': + $length = null; + break; + } + + if ($tableColumn['default'] && preg_match("('([^']+)'::)", $tableColumn['default'], $match)) { + $tableColumn['default'] = $match[1]; + } + + $options = array( + 'length' => $length, + 'notnull' => (bool) $tableColumn['isnotnull'], + 'default' => $tableColumn['default'], + 'primary' => (bool) ($tableColumn['pri'] == 't'), + 'precision' => $precision, + 'scale' => $scale, + 'fixed' => $fixed, + 'unsigned' => false, + 'autoincrement' => $autoincrement, + 'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== '' + ? $tableColumn['comment'] + : null, + ); + + $column = new Column($tableColumn['field'], Type::getType($type), $options); + + if (isset($tableColumn['collation']) && !empty($tableColumn['collation'])) { + $column->setPlatformOption('collation', $tableColumn['collation']); + } + + return $column; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SQLAnywhereSchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SQLAnywhereSchemaManager.php new file mode 100644 index 00000000..793a5fe8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SQLAnywhereSchemaManager.php @@ -0,0 +1,243 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Types\Type; + +/** + * SAP Sybase SQL Anywhere schema manager. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywhereSchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritdoc} + * + * Starts a database after creation + * as SQL Anywhere needs a database to be started + * before it can be used. + * + * @see startDatabase + */ + public function createDatabase($database) + { + parent::createDatabase($database); + $this->startDatabase($database); + } + + /** + * {@inheritdoc} + * + * Tries stopping a database before dropping + * as SQL Anywhere needs a database to be stopped + * before it can be dropped. + * + * @see stopDatabase + */ + public function dropDatabase($database) + { + $this->tryMethod('stopDatabase', $database); + parent::dropDatabase($database); + } + + /** + * Starts a database. + * + * @param string $database The name of the database to start. + */ + public function startDatabase($database) + { + $this->_execSql($this->_platform->getStartDatabaseSQL($database)); + } + + /** + * Stops a database. + * + * @param string $database The name of the database to stop. + */ + public function stopDatabase($database) + { + $this->_execSql($this->_platform->getStopDatabaseSQL($database)); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database['name']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableSequenceDefinition($sequence) + { + return new Sequence($sequence['sequence_name'], $sequence['increment_by'], $sequence['start_with']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $type = $this->_platform->getDoctrineTypeMapping($tableColumn['type']); + $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); + $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); + $precision = null; + $scale = null; + $fixed = false; + $default = null; + + if (null !== $tableColumn['default']) { + // Strip quotes from default value. + $default = preg_replace(array("/^'(.*)'$/", "/''/"), array("$1", "'"), $tableColumn['default']); + + if ('autoincrement' == $default) { + $default = null; + } + } + + switch ($tableColumn['type']) { + case 'binary': + case 'char': + case 'nchar': + $fixed = true; + } + + switch ($type) { + case 'decimal': + case 'float': + $precision = $tableColumn['length']; + $scale = $tableColumn['scale']; + } + + return new Column( + $tableColumn['column_name'], + Type::getType($type), + array( + 'length' => $type == 'string' ? $tableColumn['length'] : null, + 'precision' => $precision, + 'scale' => $scale, + 'unsigned' => (bool) $tableColumn['unsigned'], + 'fixed' => $fixed, + 'notnull' => (bool) $tableColumn['notnull'], + 'default' => $default, + 'autoincrement' => (bool) $tableColumn['autoincrement'], + 'comment' => isset($tableColumn['comment']) && '' !== $tableColumn['comment'] + ? $tableColumn['comment'] + : null, + )); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableDefinition($table) + { + return $table['table_name']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + return new ForeignKeyConstraint( + $tableForeignKey['local_columns'], + $tableForeignKey['foreign_table'], + $tableForeignKey['foreign_columns'], + $tableForeignKey['name'], + $tableForeignKey['options'] + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $foreignKeys = array(); + + foreach ($tableForeignKeys as $tableForeignKey) { + if (!isset($foreignKeys[$tableForeignKey['index_name']])) { + $foreignKeys[$tableForeignKey['index_name']] = array( + 'local_columns' => array($tableForeignKey['local_column']), + 'foreign_table' => $tableForeignKey['foreign_table'], + 'foreign_columns' => array($tableForeignKey['foreign_column']), + 'name' => $tableForeignKey['index_name'], + 'options' => array( + 'notnull' => $tableForeignKey['notnull'], + 'match' => $tableForeignKey['match'], + 'onUpdate' => $tableForeignKey['on_update'], + 'onDelete' => $tableForeignKey['on_delete'], + 'check_on_commit' => $tableForeignKey['check_on_commit'], + 'clustered' => $tableForeignKey['clustered'], + 'for_olap_workload' => $tableForeignKey['for_olap_workload'] + ) + ); + } else { + $foreignKeys[$tableForeignKey['index_name']]['local_columns'][] = $tableForeignKey['local_column']; + $foreignKeys[$tableForeignKey['index_name']]['foreign_columns'][] = $tableForeignKey['foreign_column']; + } + } + + return parent::_getPortableTableForeignKeysList($foreignKeys); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableIndexesList($tableIndexRows, $tableName = null) + { + foreach ($tableIndexRows as &$tableIndex) { + $tableIndex['primary'] = (boolean) $tableIndex['primary']; + $tableIndex['flags'] = array(); + + if ($tableIndex['clustered']) { + $tableIndex['flags'][] = 'clustered'; + } + + if ($tableIndex['with_nulls_not_distinct']) { + $tableIndex['flags'][] = 'with_nulls_not_distinct'; + } + + if ($tableIndex['for_olap_workload']) { + $tableIndex['flags'][] = 'for_olap_workload'; + } + } + + return parent::_getPortableTableIndexesList($tableIndexRows, $tableName); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableViewDefinition($view) + { + return new View( + $view['table_name'], + preg_replace('/^.*\s+as\s+SELECT(.*)/i', "SELECT$1", $view['view_def']) + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php new file mode 100644 index 00000000..100a64c2 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php @@ -0,0 +1,311 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver\DriverException; +use Doctrine\DBAL\Types\Type; + +/** + * SQL Server Schema Manager. + * + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @author Konsta Vesterinen + * @author Lukas Smith (PEAR MDB2 library) + * @author Juozas Kaziukenas + * @author Steve Müller + * @since 2.0 + */ +class SQLServerSchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritdoc} + */ + public function dropDatabase($database) + { + try { + parent::dropDatabase($database); + } catch (DBALException $exception) { + $exception = $exception->getPrevious(); + + if (! $exception instanceof DriverException) { + throw $exception; + } + + // If we have a error code 3702, the drop database operation failed + // because of active connections on the database. + // To force dropping the database, we first have to close all active connections + // on that database and issue the drop database operation again. + if ($exception->getErrorCode() !== 3702) { + throw $exception; + } + + $this->closeActiveDatabaseConnections($database); + + parent::dropDatabase($database); + } + } + + /** + * {@inheritdoc} + */ + protected function _getPortableSequenceDefinition($sequence) + { + return new Sequence($sequence['name'], $sequence['increment'], $sequence['start_value']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $dbType = strtok($tableColumn['type'], '(), '); + $fixed = null; + $length = (int) $tableColumn['length']; + $default = $tableColumn['default']; + + if (!isset($tableColumn['name'])) { + $tableColumn['name'] = ''; + } + + while ($default != ($default2 = preg_replace("/^\((.*)\)$/", '$1', $default))) { + $default = trim($default2, "'"); + + if ($default == 'getdate()') { + $default = $this->_platform->getCurrentTimestampSQL(); + } + } + + switch ($dbType) { + case 'nchar': + case 'nvarchar': + case 'ntext': + // Unicode data requires 2 bytes per character + $length = $length / 2; + break; + case 'varchar': + // TEXT type is returned as VARCHAR(MAX) with a length of -1 + if ($length == -1) { + $dbType = 'text'; + } + break; + } + + if ('char' === $dbType || 'nchar' === $dbType || 'binary' === $dbType) { + $fixed = true; + } + + $type = $this->_platform->getDoctrineTypeMapping($dbType); + $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); + $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); + + $options = array( + 'length' => ($length == 0 || !in_array($type, array('text', 'string'))) ? null : $length, + 'unsigned' => false, + 'fixed' => (bool) $fixed, + 'default' => $default !== 'NULL' ? $default : null, + 'notnull' => (bool) $tableColumn['notnull'], + 'scale' => $tableColumn['scale'], + 'precision' => $tableColumn['precision'], + 'autoincrement' => (bool) $tableColumn['autoincrement'], + 'comment' => $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null, + ); + + $column = new Column($tableColumn['name'], Type::getType($type), $options); + + if (isset($tableColumn['collation']) && $tableColumn['collation'] !== 'NULL') { + $column->setPlatformOption('collation', $tableColumn['collation']); + } + + return $column; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $foreignKeys = array(); + + foreach ($tableForeignKeys as $tableForeignKey) { + if ( ! isset($foreignKeys[$tableForeignKey['ForeignKey']])) { + $foreignKeys[$tableForeignKey['ForeignKey']] = array( + 'local_columns' => array($tableForeignKey['ColumnName']), + 'foreign_table' => $tableForeignKey['ReferenceTableName'], + 'foreign_columns' => array($tableForeignKey['ReferenceColumnName']), + 'name' => $tableForeignKey['ForeignKey'], + 'options' => array( + 'onUpdate' => str_replace('_', ' ', $tableForeignKey['update_referential_action_desc']), + 'onDelete' => str_replace('_', ' ', $tableForeignKey['delete_referential_action_desc']) + ) + ); + } else { + $foreignKeys[$tableForeignKey['ForeignKey']]['local_columns'][] = $tableForeignKey['ColumnName']; + $foreignKeys[$tableForeignKey['ForeignKey']]['foreign_columns'][] = $tableForeignKey['ReferenceColumnName']; + } + } + + return parent::_getPortableTableForeignKeysList($foreignKeys); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableIndexesList($tableIndexRows, $tableName=null) + { + foreach ($tableIndexRows as &$tableIndex) { + $tableIndex['non_unique'] = (boolean) $tableIndex['non_unique']; + $tableIndex['primary'] = (boolean) $tableIndex['primary']; + $tableIndex['flags'] = $tableIndex['flags'] ? array($tableIndex['flags']) : null; + } + + return parent::_getPortableTableIndexesList($tableIndexRows, $tableName); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + return new ForeignKeyConstraint( + $tableForeignKey['local_columns'], + $tableForeignKey['foreign_table'], + $tableForeignKey['foreign_columns'], + $tableForeignKey['name'], + $tableForeignKey['options'] + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableDefinition($table) + { + return $table['name']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database['name']; + } + + /** + * {@inheritdoc} + */ + protected function getPortableNamespaceDefinition(array $namespace) + { + return $namespace['name']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableViewDefinition($view) + { + // @todo + return new View($view['name'], null); + } + + /** + * {@inheritdoc} + */ + public function listTableIndexes($table) + { + $sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase()); + + try { + $tableIndexes = $this->_conn->fetchAll($sql); + } catch (\PDOException $e) { + if ($e->getCode() == "IMSSP") { + return array(); + } else { + throw $e; + } + } catch (DBALException $e) { + if (strpos($e->getMessage(), 'SQLSTATE [01000, 15472]') === 0) { + return array(); + } else { + throw $e; + } + } + + return $this->_getPortableTableIndexesList($tableIndexes, $table); + } + + /** + * {@inheritdoc} + */ + public function alterTable(TableDiff $tableDiff) + { + if (count($tableDiff->removedColumns) > 0) { + foreach ($tableDiff->removedColumns as $col) { + $columnConstraintSql = $this->getColumnConstraintSQL($tableDiff->name, $col->getName()); + foreach ($this->_conn->fetchAll($columnConstraintSql) as $constraint) { + $this->_conn->exec("ALTER TABLE $tableDiff->name DROP CONSTRAINT " . $constraint['Name']); + } + } + } + + parent::alterTable($tableDiff); + } + + /** + * Returns the SQL to retrieve the constraints for a given column. + * + * @param string $table + * @param string $column + * + * @return string + */ + private function getColumnConstraintSQL($table, $column) + { + return "SELECT SysObjects.[Name] + FROM SysObjects INNER JOIN (SELECT [Name],[ID] FROM SysObjects WHERE XType = 'U') AS Tab + ON Tab.[ID] = Sysobjects.[Parent_Obj] + INNER JOIN sys.default_constraints DefCons ON DefCons.[object_id] = Sysobjects.[ID] + INNER JOIN SysColumns Col ON Col.[ColID] = DefCons.[parent_column_id] AND Col.[ID] = Tab.[ID] + WHERE Col.[Name] = " . $this->_conn->quote($column) ." AND Tab.[Name] = " . $this->_conn->quote($table) . " + ORDER BY Col.[Name]"; + } + + /** + * Closes currently active connections on the given database. + * + * This is useful to force DROP DATABASE operations which could fail because of active connections. + * + * @param string $database The name of the database to close currently active connections for. + * + * @return void + */ + private function closeActiveDatabaseConnections($database) + { + $database = new Identifier($database); + + $this->_execSql( + sprintf( + 'ALTER DATABASE %s SET SINGLE_USER WITH ROLLBACK IMMEDIATE', + $database->getQuotedName($this->_platform) + ) + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Schema.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Schema.php new file mode 100644 index 00000000..b7e24fe9 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Schema.php @@ -0,0 +1,510 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector; +use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector; +use Doctrine\DBAL\Schema\Visitor\NamespaceVisitor; +use Doctrine\DBAL\Schema\Visitor\Visitor; +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Object representation of a database schema. + * + * Different vendors have very inconsistent naming with regard to the concept + * of a "schema". Doctrine understands a schema as the entity that conceptually + * wraps a set of database objects such as tables, sequences, indexes and + * foreign keys that belong to each other into a namespace. A Doctrine Schema + * has nothing to do with the "SCHEMA" defined as in PostgreSQL, it is more + * related to the concept of "DATABASE" that exists in MySQL and PostgreSQL. + * + * Every asset in the doctrine schema has a name. A name consists of either a + * namespace.local name pair or just a local unqualified name. + * + * The abstraction layer that covers a PostgreSQL schema is the namespace of an + * database object (asset). A schema can have a name, which will be used as + * default namespace for the unqualified database objects that are created in + * the schema. + * + * In the case of MySQL where cross-database queries are allowed this leads to + * databases being "misinterpreted" as namespaces. This is intentional, however + * the CREATE/DROP SQL visitors will just filter this queries and do not + * execute them. Only the queries for the currently connected database are + * executed. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class Schema extends AbstractAsset +{ + /** + * The namespaces in this schema. + * + * @var array + */ + private $namespaces = array(); + + /** + * @var \Doctrine\DBAL\Schema\Table[] + */ + protected $_tables = array(); + + /** + * @var \Doctrine\DBAL\Schema\Sequence[] + */ + protected $_sequences = array(); + + /** + * @var \Doctrine\DBAL\Schema\SchemaConfig + */ + protected $_schemaConfig = false; + + /** + * @param \Doctrine\DBAL\Schema\Table[] $tables + * @param \Doctrine\DBAL\Schema\Sequence[] $sequences + * @param \Doctrine\DBAL\Schema\SchemaConfig $schemaConfig + * @param array $namespaces + */ + public function __construct( + array $tables = array(), + array $sequences = array(), + SchemaConfig $schemaConfig = null, + array $namespaces = array() + ) { + if ($schemaConfig == null) { + $schemaConfig = new SchemaConfig(); + } + $this->_schemaConfig = $schemaConfig; + $this->_setName($schemaConfig->getName() ?: 'public'); + + foreach ($namespaces as $namespace) { + $this->createNamespace($namespace); + } + + foreach ($tables as $table) { + $this->_addTable($table); + } + + foreach ($sequences as $sequence) { + $this->_addSequence($sequence); + } + } + + /** + * @return boolean + */ + public function hasExplicitForeignKeyIndexes() + { + return $this->_schemaConfig->hasExplicitForeignKeyIndexes(); + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * + * @return void + * + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + protected function _addTable(Table $table) + { + $namespaceName = $table->getNamespaceName(); + $tableName = $table->getFullQualifiedName($this->getName()); + + if (isset($this->_tables[$tableName])) { + throw SchemaException::tableAlreadyExists($tableName); + } + + if ( ! $table->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName)) { + $this->createNamespace($namespaceName); + } + + $this->_tables[$tableName] = $table; + $table->setSchemaConfig($this->_schemaConfig); + } + + /** + * @param \Doctrine\DBAL\Schema\Sequence $sequence + * + * @return void + * + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + protected function _addSequence(Sequence $sequence) + { + $namespaceName = $sequence->getNamespaceName(); + $seqName = $sequence->getFullQualifiedName($this->getName()); + + if (isset($this->_sequences[$seqName])) { + throw SchemaException::sequenceAlreadyExists($seqName); + } + + if ( ! $sequence->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName)) { + $this->createNamespace($namespaceName); + } + + $this->_sequences[$seqName] = $sequence; + } + + /** + * Returns the namespaces of this schema. + * + * @return array A list of namespace names. + */ + public function getNamespaces() + { + return $this->namespaces; + } + + /** + * Gets all tables of this schema. + * + * @return \Doctrine\DBAL\Schema\Table[] + */ + public function getTables() + { + return $this->_tables; + } + + /** + * @param string $tableName + * + * @return \Doctrine\DBAL\Schema\Table + * + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + public function getTable($tableName) + { + $tableName = $this->getFullQualifiedAssetName($tableName); + if (!isset($this->_tables[$tableName])) { + throw SchemaException::tableDoesNotExist($tableName); + } + + return $this->_tables[$tableName]; + } + + /** + * @param string $name + * + * @return string + */ + private function getFullQualifiedAssetName($name) + { + $name = $this->getUnquotedAssetName($name); + + if (strpos($name, ".") === false) { + $name = $this->getName() . "." . $name; + } + + return strtolower($name); + } + + /** + * Returns the unquoted representation of a given asset name. + * + * @param string $assetName Quoted or unquoted representation of an asset name. + * + * @return string + */ + private function getUnquotedAssetName($assetName) + { + if ($this->isIdentifierQuoted($assetName)) { + return $this->trimQuotes($assetName); + } + + return $assetName; + } + + /** + * Does this schema have a namespace with the given name? + * + * @param string $namespaceName + * + * @return boolean + */ + public function hasNamespace($namespaceName) + { + $namespaceName = strtolower($this->getUnquotedAssetName($namespaceName)); + + return isset($this->namespaces[$namespaceName]); + } + + /** + * Does this schema have a table with the given name? + * + * @param string $tableName + * + * @return boolean + */ + public function hasTable($tableName) + { + $tableName = $this->getFullQualifiedAssetName($tableName); + + return isset($this->_tables[$tableName]); + } + + /** + * Gets all table names, prefixed with a schema name, even the default one if present. + * + * @return array + */ + public function getTableNames() + { + return array_keys($this->_tables); + } + + /** + * @param string $sequenceName + * + * @return boolean + */ + public function hasSequence($sequenceName) + { + $sequenceName = $this->getFullQualifiedAssetName($sequenceName); + + return isset($this->_sequences[$sequenceName]); + } + + /** + * @param string $sequenceName + * + * @return \Doctrine\DBAL\Schema\Sequence + * + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + public function getSequence($sequenceName) + { + $sequenceName = $this->getFullQualifiedAssetName($sequenceName); + if (!$this->hasSequence($sequenceName)) { + throw SchemaException::sequenceDoesNotExist($sequenceName); + } + + return $this->_sequences[$sequenceName]; + } + + /** + * @return \Doctrine\DBAL\Schema\Sequence[] + */ + public function getSequences() + { + return $this->_sequences; + } + + /** + * Creates a new namespace. + * + * @param string $namespaceName The name of the namespace to create. + * + * @return \Doctrine\DBAL\Schema\Schema This schema instance. + */ + public function createNamespace($namespaceName) + { + $unquotedNamespaceName = strtolower($this->getUnquotedAssetName($namespaceName)); + + if (isset($this->namespaces[$unquotedNamespaceName])) { + throw SchemaException::namespaceAlreadyExists($unquotedNamespaceName); + } + + $this->namespaces[$unquotedNamespaceName] = $namespaceName; + + return $this; + } + + /** + * Creates a new table. + * + * @param string $tableName + * + * @return \Doctrine\DBAL\Schema\Table + */ + public function createTable($tableName) + { + $table = new Table($tableName); + $this->_addTable($table); + + foreach ($this->_schemaConfig->getDefaultTableOptions() as $name => $value) { + $table->addOption($name, $value); + } + + return $table; + } + + /** + * Renames a table. + * + * @param string $oldTableName + * @param string $newTableName + * + * @return \Doctrine\DBAL\Schema\Schema + */ + public function renameTable($oldTableName, $newTableName) + { + $table = $this->getTable($oldTableName); + $table->_setName($newTableName); + + $this->dropTable($oldTableName); + $this->_addTable($table); + + return $this; + } + + /** + * Drops a table from the schema. + * + * @param string $tableName + * + * @return \Doctrine\DBAL\Schema\Schema + */ + public function dropTable($tableName) + { + $tableName = $this->getFullQualifiedAssetName($tableName); + $this->getTable($tableName); + unset($this->_tables[$tableName]); + + return $this; + } + + /** + * Creates a new sequence. + * + * @param string $sequenceName + * @param integer $allocationSize + * @param integer $initialValue + * + * @return \Doctrine\DBAL\Schema\Sequence + */ + public function createSequence($sequenceName, $allocationSize=1, $initialValue=1) + { + $seq = new Sequence($sequenceName, $allocationSize, $initialValue); + $this->_addSequence($seq); + + return $seq; + } + + /** + * @param string $sequenceName + * + * @return \Doctrine\DBAL\Schema\Schema + */ + public function dropSequence($sequenceName) + { + $sequenceName = $this->getFullQualifiedAssetName($sequenceName); + unset($this->_sequences[$sequenceName]); + + return $this; + } + + /** + * Returns an array of necessary SQL queries to create the schema on the given platform. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return array + */ + public function toSql(AbstractPlatform $platform) + { + $sqlCollector = new CreateSchemaSqlCollector($platform); + $this->visit($sqlCollector); + + return $sqlCollector->getQueries(); + } + + /** + * Return an array of necessary SQL queries to drop the schema on the given platform. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return array + */ + public function toDropSql(AbstractPlatform $platform) + { + $dropSqlCollector = new DropSchemaSqlCollector($platform); + $this->visit($dropSqlCollector); + + return $dropSqlCollector->getQueries(); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $toSchema + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return array + */ + public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform) + { + $comparator = new Comparator(); + $schemaDiff = $comparator->compare($this, $toSchema); + + return $schemaDiff->toSql($platform); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $fromSchema + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return array + */ + public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform) + { + $comparator = new Comparator(); + $schemaDiff = $comparator->compare($fromSchema, $this); + + return $schemaDiff->toSql($platform); + } + + /** + * @param \Doctrine\DBAL\Schema\Visitor\Visitor $visitor + * + * @return void + */ + public function visit(Visitor $visitor) + { + $visitor->acceptSchema($this); + + if ($visitor instanceof NamespaceVisitor) { + foreach ($this->namespaces as $namespace) { + $visitor->acceptNamespace($namespace); + } + } + + foreach ($this->_tables as $table) { + $table->visit($visitor); + } + + foreach ($this->_sequences as $sequence) { + $sequence->visit($visitor); + } + } + + /** + * Cloning a Schema triggers a deep clone of all related assets. + * + * @return void + */ + public function __clone() + { + foreach ($this->_tables as $k => $table) { + $this->_tables[$k] = clone $table; + } + foreach ($this->_sequences as $k => $sequence) { + $this->_sequences[$k] = clone $sequence; + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaConfig.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaConfig.php new file mode 100644 index 00000000..cc23237d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaConfig.php @@ -0,0 +1,129 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * Configuration for a Schema. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class SchemaConfig +{ + /** + * @var boolean + */ + protected $hasExplicitForeignKeyIndexes = false; + + /** + * @var integer + */ + protected $maxIdentifierLength = 63; + + /** + * @var string + */ + protected $name; + + /** + * @var array + */ + protected $defaultTableOptions = array(); + + /** + * @return boolean + */ + public function hasExplicitForeignKeyIndexes() + { + return $this->hasExplicitForeignKeyIndexes; + } + + /** + * @param boolean $flag + * + * @return void + */ + public function setExplicitForeignKeyIndexes($flag) + { + $this->hasExplicitForeignKeyIndexes = (bool) $flag; + } + + /** + * @param integer $length + * + * @return void + */ + public function setMaxIdentifierLength($length) + { + $this->maxIdentifierLength = (int) $length; + } + + /** + * @return integer + */ + public function getMaxIdentifierLength() + { + return $this->maxIdentifierLength; + } + + /** + * Gets the default namespace of schema objects. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the default namespace name of schema objects. + * + * @param string $name The value to set. + * + * @return void + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Gets the default options that are passed to Table instances created with + * Schema#createTable(). + * + * @return array + */ + public function getDefaultTableOptions() + { + return $this->defaultTableOptions; + } + + /** + * @param array $defaultTableOptions + * + * @return void + */ + public function setDefaultTableOptions(array $defaultTableOptions) + { + $this->defaultTableOptions = $defaultTableOptions; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaDiff.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaDiff.php new file mode 100644 index 00000000..23eccdad --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaDiff.php @@ -0,0 +1,204 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use \Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Schema Diff. + * + * @link www.doctrine-project.org + * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + * @since 2.0 + * @author Benjamin Eberlei + */ +class SchemaDiff +{ + /** + * @var \Doctrine\DBAL\Schema\Schema + */ + public $fromSchema; + + /** + * All added namespaces. + * + * @var string[] + */ + public $newNamespaces = array(); + + /** + * All removed namespaces. + * + * @var string[] + */ + public $removedNamespaces = array(); + + /** + * All added tables. + * + * @var \Doctrine\DBAL\Schema\Table[] + */ + public $newTables = array(); + + /** + * All changed tables. + * + * @var \Doctrine\DBAL\Schema\TableDiff[] + */ + public $changedTables = array(); + + /** + * All removed tables. + * + * @var \Doctrine\DBAL\Schema\Table[] + */ + public $removedTables = array(); + + /** + * @var \Doctrine\DBAL\Schema\Sequence[] + */ + public $newSequences = array(); + + /** + * @var \Doctrine\DBAL\Schema\Sequence[] + */ + public $changedSequences = array(); + + /** + * @var \Doctrine\DBAL\Schema\Sequence[] + */ + public $removedSequences = array(); + + /** + * @var \Doctrine\DBAL\Schema\ForeignKeyConstraint[] + */ + public $orphanedForeignKeys = array(); + + /** + * Constructs an SchemaDiff object. + * + * @param \Doctrine\DBAL\Schema\Table[] $newTables + * @param \Doctrine\DBAL\Schema\TableDiff[] $changedTables + * @param \Doctrine\DBAL\Schema\Table[] $removedTables + * @param \Doctrine\DBAL\Schema\Schema|null $fromSchema + */ + public function __construct($newTables = array(), $changedTables = array(), $removedTables = array(), Schema $fromSchema = null) + { + $this->newTables = $newTables; + $this->changedTables = $changedTables; + $this->removedTables = $removedTables; + $this->fromSchema = $fromSchema; + } + + /** + * The to save sql mode ensures that the following things don't happen: + * + * 1. Tables are deleted + * 2. Sequences are deleted + * 3. Foreign Keys which reference tables that would otherwise be deleted. + * + * This way it is ensured that assets are deleted which might not be relevant to the metadata schema at all. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return array + */ + public function toSaveSql(AbstractPlatform $platform) + { + return $this->_toSql($platform, true); + } + + /** + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return array + */ + public function toSql(AbstractPlatform $platform) + { + return $this->_toSql($platform, false); + } + + /** + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * @param boolean $saveMode + * + * @return array + */ + protected function _toSql(AbstractPlatform $platform, $saveMode = false) + { + $sql = array(); + + if ($platform->supportsSchemas()) { + foreach ($this->newNamespaces as $newNamespace) { + $sql[] = $platform->getCreateSchemaSQL($newNamespace); + } + } + + if ($platform->supportsForeignKeyConstraints() && $saveMode == false) { + foreach ($this->orphanedForeignKeys as $orphanedForeignKey) { + $sql[] = $platform->getDropForeignKeySQL($orphanedForeignKey, $orphanedForeignKey->getLocalTable()); + } + } + + if ($platform->supportsSequences() == true) { + foreach ($this->changedSequences as $sequence) { + $sql[] = $platform->getAlterSequenceSQL($sequence); + } + + if ($saveMode === false) { + foreach ($this->removedSequences as $sequence) { + $sql[] = $platform->getDropSequenceSQL($sequence); + } + } + + foreach ($this->newSequences as $sequence) { + $sql[] = $platform->getCreateSequenceSQL($sequence); + } + } + + $foreignKeySql = array(); + foreach ($this->newTables as $table) { + $sql = array_merge( + $sql, + $platform->getCreateTableSQL($table, AbstractPlatform::CREATE_INDEXES) + ); + + if ($platform->supportsForeignKeyConstraints()) { + foreach ($table->getForeignKeys() as $foreignKey) { + $foreignKeySql[] = $platform->getCreateForeignKeySQL($foreignKey, $table); + } + } + } + $sql = array_merge($sql, $foreignKeySql); + + if ($saveMode === false) { + foreach ($this->removedTables as $table) { + $sql[] = $platform->getDropTableSQL($table); + } + } + + foreach ($this->changedTables as $tableDiff) { + $sql = array_merge($sql, $platform->getAlterTableSQL($tableDiff)); + } + + return $sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaException.php new file mode 100644 index 00000000..152f374d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaException.php @@ -0,0 +1,181 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +class SchemaException extends \Doctrine\DBAL\DBALException +{ + const TABLE_DOESNT_EXIST = 10; + const TABLE_ALREADY_EXISTS = 20; + const COLUMN_DOESNT_EXIST = 30; + const COLUMN_ALREADY_EXISTS = 40; + const INDEX_DOESNT_EXIST = 50; + const INDEX_ALREADY_EXISTS = 60; + const SEQUENCE_DOENST_EXIST = 70; + const SEQUENCE_ALREADY_EXISTS = 80; + const INDEX_INVALID_NAME = 90; + const FOREIGNKEY_DOESNT_EXIST = 100; + const NAMESPACE_ALREADY_EXISTS = 110; + + /** + * @param string $tableName + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function tableDoesNotExist($tableName) + { + return new self("There is no table with name '".$tableName."' in the schema.", self::TABLE_DOESNT_EXIST); + } + + /** + * @param string $indexName + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function indexNameInvalid($indexName) + { + return new self("Invalid index-name $indexName given, has to be [a-zA-Z0-9_]", self::INDEX_INVALID_NAME); + } + + /** + * @param string $indexName + * @param string $table + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function indexDoesNotExist($indexName, $table) + { + return new self("Index '$indexName' does not exist on table '$table'.", self::INDEX_DOESNT_EXIST); + } + + /** + * @param string $indexName + * @param string $table + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function indexAlreadyExists($indexName, $table) + { + return new self("An index with name '$indexName' was already defined on table '$table'.", self::INDEX_ALREADY_EXISTS); + } + + /** + * @param string $columnName + * @param string $table + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function columnDoesNotExist($columnName, $table) + { + return new self("There is no column with name '$columnName' on table '$table'.", self::COLUMN_DOESNT_EXIST); + } + + /** + * @param string $namespaceName + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function namespaceAlreadyExists($namespaceName) + { + return new self( + sprintf("The namespace with name '%s' already exists.", $namespaceName), + self::NAMESPACE_ALREADY_EXISTS + ); + } + + /** + * @param string $tableName + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function tableAlreadyExists($tableName) + { + return new self("The table with name '".$tableName."' already exists.", self::TABLE_ALREADY_EXISTS); + } + + /** + * @param string $tableName + * @param string $columnName + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function columnAlreadyExists($tableName, $columnName) + { + return new self( + "The column '".$columnName."' on table '".$tableName."' already exists.", self::COLUMN_ALREADY_EXISTS + ); + } + + /** + * @param string $sequenceName + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function sequenceAlreadyExists($sequenceName) + { + return new self("The sequence '".$sequenceName."' already exists.", self::SEQUENCE_ALREADY_EXISTS); + } + + /** + * @param string $sequenceName + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function sequenceDoesNotExist($sequenceName) + { + return new self("There exists no sequence with the name '".$sequenceName."'.", self::SEQUENCE_DOENST_EXIST); + } + + /** + * @param string $fkName + * @param string $table + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function foreignKeyDoesNotExist($fkName, $table) + { + return new self("There exists no foreign key with the name '$fkName' on table '$table'.", self::FOREIGNKEY_DOESNT_EXIST); + } + + /** + * @param \Doctrine\DBAL\Schema\Table $localTable + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function namedForeignKeyRequired(Table $localTable, ForeignKeyConstraint $foreignKey) + { + return new self( + "The performed schema operation on ".$localTable->getName()." requires a named foreign key, ". + "but the given foreign key from (".implode(", ", $foreignKey->getColumns()).") onto foreign table ". + "'".$foreignKey->getForeignTableName()."' (".implode(", ", $foreignKey->getForeignColumns()).") is currently ". + "unnamed." + ); + } + + /** + * @param string $changeName + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function alterTableChangeNotSupported($changeName) + { + return new self("Alter table change not supported, given '$changeName'"); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Sequence.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Sequence.php new file mode 100644 index 00000000..cc5b5016 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Sequence.php @@ -0,0 +1,166 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Schema\Visitor\Visitor; + +/** + * Sequence structure. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class Sequence extends AbstractAsset +{ + /** + * @var integer + */ + protected $allocationSize = 1; + + /** + * @var integer + */ + protected $initialValue = 1; + + /** + * @var integer|null + */ + protected $cache = null; + + /** + * @param string $name + * @param integer $allocationSize + * @param integer $initialValue + * @param integer|null $cache + */ + public function __construct($name, $allocationSize = 1, $initialValue = 1, $cache = null) + { + $this->_setName($name); + $this->allocationSize = is_numeric($allocationSize) ? $allocationSize : 1; + $this->initialValue = is_numeric($initialValue) ? $initialValue : 1; + $this->cache = $cache; + } + + /** + * @return integer + */ + public function getAllocationSize() + { + return $this->allocationSize; + } + + /** + * @return integer + */ + public function getInitialValue() + { + return $this->initialValue; + } + + /** + * @return integer|null + */ + public function getCache() + { + return $this->cache; + } + + /** + * @param integer $allocationSize + * + * @return \Doctrine\DBAL\Schema\Sequence + */ + public function setAllocationSize($allocationSize) + { + $this->allocationSize = is_numeric($allocationSize) ? $allocationSize : 1; + + return $this; + } + + /** + * @param integer $initialValue + * + * @return \Doctrine\DBAL\Schema\Sequence + */ + public function setInitialValue($initialValue) + { + $this->initialValue = is_numeric($initialValue) ? $initialValue : 1; + + return $this; + } + + /** + * @param integer $cache + * + * @return \Doctrine\DBAL\Schema\Sequence + */ + public function setCache($cache) + { + $this->cache = $cache; + + return $this; + } + + /** + * Checks if this sequence is an autoincrement sequence for a given table. + * + * This is used inside the comparator to not report sequences as missing, + * when the "from" schema implicitly creates the sequences. + * + * @param \Doctrine\DBAL\Schema\Table $table + * + * @return boolean + */ + public function isAutoIncrementsFor(Table $table) + { + if ( ! $table->hasPrimaryKey()) { + return false; + } + + $pkColumns = $table->getPrimaryKey()->getColumns(); + + if (count($pkColumns) != 1) { + return false; + } + + $column = $table->getColumn($pkColumns[0]); + + if ( ! $column->getAutoincrement()) { + return false; + } + + $sequenceName = $this->getShortestName($table->getNamespaceName()); + $tableName = $table->getShortestName($table->getNamespaceName()); + $tableSequenceName = sprintf('%s_%s_seq', $tableName, $pkColumns[0]); + + return $tableSequenceName === $sequenceName; + } + + /** + * @param \Doctrine\DBAL\Schema\Visitor\Visitor $visitor + * + * @return void + */ + public function visit(Visitor $visitor) + { + $visitor->acceptSequence($this); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php new file mode 100644 index 00000000..7cf166e3 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php @@ -0,0 +1,433 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Types\StringType; +use Doctrine\DBAL\Types\TextType; + +/** + * Sqlite SchemaManager. + * + * @author Konsta Vesterinen + * @author Lukas Smith (PEAR MDB2 library) + * @author Jonathan H. Wage + * @author Martin Hasoň + * @since 2.0 + */ +class SqliteSchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritdoc} + */ + public function dropDatabase($database) + { + if (file_exists($database)) { + unlink($database); + } + } + + /** + * {@inheritdoc} + */ + public function createDatabase($database) + { + $params = $this->_conn->getParams(); + $driver = $params['driver']; + $options = array( + 'driver' => $driver, + 'path' => $database + ); + $conn = \Doctrine\DBAL\DriverManager::getConnection($options); + $conn->connect(); + $conn->close(); + } + + /** + * {@inheritdoc} + */ + public function renameTable($name, $newName) + { + $tableDiff = new TableDiff($name); + $tableDiff->fromTable = $this->listTableDetails($name); + $tableDiff->newName = $newName; + $this->alterTable($tableDiff); + } + + /** + * {@inheritdoc} + */ + public function createForeignKey(ForeignKeyConstraint $foreignKey, $table) + { + $tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table); + $tableDiff->addedForeignKeys[] = $foreignKey; + + $this->alterTable($tableDiff); + } + + /** + * {@inheritdoc} + */ + public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table) + { + $tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table); + $tableDiff->changedForeignKeys[] = $foreignKey; + + $this->alterTable($tableDiff); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($foreignKey, $table) + { + $tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table); + $tableDiff->removedForeignKeys[] = $foreignKey; + + $this->alterTable($tableDiff); + } + + /** + * {@inheritdoc} + */ + public function listTableForeignKeys($table, $database = null) + { + if (null === $database) { + $database = $this->_conn->getDatabase(); + } + $sql = $this->_platform->getListTableForeignKeysSQL($table, $database); + $tableForeignKeys = $this->_conn->fetchAll($sql); + + if ( ! empty($tableForeignKeys)) { + $createSql = $this->_conn->fetchAll("SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' AND name = '$table'"); + $createSql = isset($createSql[0]['sql']) ? $createSql[0]['sql'] : ''; + if (preg_match_all('# + (?:CONSTRAINT\s+([^\s]+)\s+)? + (?:FOREIGN\s+KEY[^\)]+\)\s*)? + REFERENCES\s+[^\s]+\s+(?:\([^\)]+\))? + (?: + [^,]*? + (NOT\s+DEFERRABLE|DEFERRABLE) + (?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))? + )?#isx', + $createSql, $match)) { + + $names = array_reverse($match[1]); + $deferrable = array_reverse($match[2]); + $deferred = array_reverse($match[3]); + } else { + $names = $deferrable = $deferred = array(); + } + + foreach ($tableForeignKeys as $key => $value) { + $id = $value['id']; + $tableForeignKeys[$key]['constraint_name'] = isset($names[$id]) && '' != $names[$id] ? $names[$id] : $id; + $tableForeignKeys[$key]['deferrable'] = isset($deferrable[$id]) && 'deferrable' == strtolower($deferrable[$id]) ? true : false; + $tableForeignKeys[$key]['deferred'] = isset($deferred[$id]) && 'deferred' == strtolower($deferred[$id]) ? true : false; + } + } + + return $this->_getPortableTableForeignKeysList($tableForeignKeys); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableDefinition($table) + { + return $table['name']; + } + + /** + * {@inheritdoc} + * + * @license New BSD License + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) + { + $indexBuffer = array(); + + // fetch primary + $stmt = $this->_conn->executeQuery("PRAGMA TABLE_INFO ('$tableName')"); + $indexArray = $stmt->fetchAll(\PDO::FETCH_ASSOC); + usort($indexArray, function($a, $b) { + if ($a['pk'] == $b['pk']) { + return $a['cid'] - $b['cid']; + } + + return $a['pk'] - $b['pk']; + }); + foreach ($indexArray as $indexColumnRow) { + if ($indexColumnRow['pk'] != "0") { + $indexBuffer[] = array( + 'key_name' => 'primary', + 'primary' => true, + 'non_unique' => false, + 'column_name' => $indexColumnRow['name'] + ); + } + } + + // fetch regular indexes + foreach ($tableIndexes as $tableIndex) { + // Ignore indexes with reserved names, e.g. autoindexes + if (strpos($tableIndex['name'], 'sqlite_') !== 0) { + $keyName = $tableIndex['name']; + $idx = array(); + $idx['key_name'] = $keyName; + $idx['primary'] = false; + $idx['non_unique'] = $tableIndex['unique']?false:true; + + $stmt = $this->_conn->executeQuery("PRAGMA INDEX_INFO ('{$keyName}')"); + $indexArray = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + foreach ($indexArray as $indexColumnRow) { + $idx['column_name'] = $indexColumnRow['name']; + $indexBuffer[] = $idx; + } + } + } + + return parent::_getPortableTableIndexesList($indexBuffer, $tableName); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableIndexDefinition($tableIndex) + { + return array( + 'name' => $tableIndex['name'], + 'unique' => (bool) $tableIndex['unique'] + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnList($table, $database, $tableColumns) + { + $list = parent::_getPortableTableColumnList($table, $database, $tableColumns); + + // find column with autoincrement + $autoincrementColumn = null; + $autoincrementCount = 0; + + foreach ($tableColumns as $tableColumn) { + if ('0' != $tableColumn['pk']) { + $autoincrementCount++; + if (null === $autoincrementColumn && 'integer' == strtolower($tableColumn['type'])) { + $autoincrementColumn = $tableColumn['name']; + } + } + } + + if (1 == $autoincrementCount && null !== $autoincrementColumn) { + foreach ($list as $column) { + if ($autoincrementColumn == $column->getName()) { + $column->setAutoincrement(true); + } + } + } + + // inspect column collation + $createSql = $this->_conn->fetchAll("SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' AND name = '$table'"); + $createSql = isset($createSql[0]['sql']) ? $createSql[0]['sql'] : ''; + + foreach ($list as $columnName => $column) { + $type = $column->getType(); + + if ($type instanceof StringType || $type instanceof TextType) { + $column->setPlatformOption('collation', $this->parseColumnCollationFromSQL($columnName, $createSql) ?: 'BINARY'); + } + } + + return $list; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $parts = explode('(', $tableColumn['type']); + $tableColumn['type'] = trim($parts[0]); + if (isset($parts[1])) { + $length = trim($parts[1], ')'); + $tableColumn['length'] = $length; + } + + $dbType = strtolower($tableColumn['type']); + $length = isset($tableColumn['length']) ? $tableColumn['length'] : null; + $unsigned = false; + + if (strpos($dbType, ' unsigned') !== false) { + $dbType = str_replace(' unsigned', '', $dbType); + $unsigned = true; + } + + $fixed = false; + $type = $this->_platform->getDoctrineTypeMapping($dbType); + $default = $tableColumn['dflt_value']; + if ($default == 'NULL') { + $default = null; + } + if ($default !== null) { + // SQLite returns strings wrapped in single quotes, so we need to strip them + $default = preg_replace("/^'(.*)'$/", '\1', $default); + } + $notnull = (bool) $tableColumn['notnull']; + + if ( ! isset($tableColumn['name'])) { + $tableColumn['name'] = ''; + } + + $precision = null; + $scale = null; + + switch ($dbType) { + case 'char': + $fixed = true; + break; + case 'float': + case 'double': + case 'real': + case 'decimal': + case 'numeric': + if (isset($tableColumn['length'])) { + if (strpos($tableColumn['length'], ',') === false) { + $tableColumn['length'] .= ",0"; + } + list($precision, $scale) = array_map('trim', explode(',', $tableColumn['length'])); + } + $length = null; + break; + } + + $options = array( + 'length' => $length, + 'unsigned' => (bool) $unsigned, + 'fixed' => $fixed, + 'notnull' => $notnull, + 'default' => $default, + 'precision' => $precision, + 'scale' => $scale, + 'autoincrement' => false, + ); + + return new Column($tableColumn['name'], \Doctrine\DBAL\Types\Type::getType($type), $options); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableViewDefinition($view) + { + return new View($view['name'], $view['sql']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $list = array(); + foreach ($tableForeignKeys as $value) { + $value = array_change_key_case($value, CASE_LOWER); + $name = $value['constraint_name']; + if ( ! isset($list[$name])) { + if ( ! isset($value['on_delete']) || $value['on_delete'] == "RESTRICT") { + $value['on_delete'] = null; + } + if ( ! isset($value['on_update']) || $value['on_update'] == "RESTRICT") { + $value['on_update'] = null; + } + + $list[$name] = array( + 'name' => $name, + 'local' => array(), + 'foreign' => array(), + 'foreignTable' => $value['table'], + 'onDelete' => $value['on_delete'], + 'onUpdate' => $value['on_update'], + 'deferrable' => $value['deferrable'], + 'deferred'=> $value['deferred'], + ); + } + $list[$name]['local'][] = $value['from']; + $list[$name]['foreign'][] = $value['to']; + } + + $result = array(); + foreach ($list as $constraint) { + $result[] = new ForeignKeyConstraint( + array_values($constraint['local']), $constraint['foreignTable'], + array_values($constraint['foreign']), $constraint['name'], + array( + 'onDelete' => $constraint['onDelete'], + 'onUpdate' => $constraint['onUpdate'], + 'deferrable' => $constraint['deferrable'], + 'deferred'=> $constraint['deferred'], + ) + ); + } + + return $result; + } + + /** + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return \Doctrine\DBAL\Schema\TableDiff + * + * @throws \Doctrine\DBAL\DBALException + */ + private function getTableDiffForAlterForeignKey(ForeignKeyConstraint $foreignKey, $table) + { + if ( ! $table instanceof Table) { + $tableDetails = $this->tryMethod('listTableDetails', $table); + if (false === $table) { + throw new DBALException(sprintf('Sqlite schema manager requires to modify foreign keys table definition "%s".', $table)); + } + + $table = $tableDetails; + } + + $tableDiff = new TableDiff($table->getName()); + $tableDiff->fromTable = $table; + + return $tableDiff; + } + + private function parseColumnCollationFromSQL($column, $sql) + { + if (preg_match( + '{(?:'.preg_quote($column).'|'.preg_quote($this->_platform->quoteSingleIdentifier($column)).') + [^,(]+(?:\([^()]+\)[^,]*)? + (?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)* + COLLATE\s+["\']?([^\s,"\')]+)}isx', $sql, $match)) { + return $match[1]; + } + + return false; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/AbstractSchemaSynchronizer.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/AbstractSchemaSynchronizer.php new file mode 100644 index 00000000..3158df47 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/AbstractSchemaSynchronizer.php @@ -0,0 +1,65 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Synchronizer; + +use Doctrine\DBAL\Connection; + +/** + * Abstract schema synchronizer with methods for executing batches of SQL. + */ +abstract class AbstractSchemaSynchronizer implements SchemaSynchronizer +{ + /** + * @var \Doctrine\DBAL\Connection + */ + protected $conn; + + /** + * @param \Doctrine\DBAL\Connection $conn + */ + public function __construct(Connection $conn) + { + $this->conn = $conn; + } + + /** + * @param array $sql + */ + protected function processSqlSafely(array $sql) + { + foreach ($sql as $s) { + try { + $this->conn->exec($s); + } catch (\Exception $e) { + + } + } + } + + /** + * @param array $sql + */ + protected function processSql(array $sql) + { + foreach ($sql as $s) { + $this->conn->exec($s); + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SchemaSynchronizer.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SchemaSynchronizer.php new file mode 100644 index 00000000..1a5552ea --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SchemaSynchronizer.php @@ -0,0 +1,101 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Synchronizer; + +use Doctrine\DBAL\Schema\Schema; + +/** + * The synchronizer knows how to synchronize a schema with the configured + * database. + * + * @author Benjamin Eberlei + */ +interface SchemaSynchronizer +{ + /** + * Gets the SQL statements that can be executed to create the schema. + * + * @param \Doctrine\DBAL\Schema\Schema $createSchema + * + * @return array + */ + function getCreateSchema(Schema $createSchema); + + /** + * Gets the SQL Statements to update given schema with the underlying db. + * + * @param \Doctrine\DBAL\Schema\Schema $toSchema + * @param boolean $noDrops + * + * @return array + */ + function getUpdateSchema(Schema $toSchema, $noDrops = false); + + /** + * Gets the SQL Statements to drop the given schema from underlying db. + * + * @param \Doctrine\DBAL\Schema\Schema $dropSchema + * + * @return array + */ + function getDropSchema(Schema $dropSchema); + + /** + * Gets the SQL statements to drop all schema assets from underlying db. + * + * @return array + */ + function getDropAllSchema(); + + /** + * Creates the Schema. + * + * @param \Doctrine\DBAL\Schema\Schema $createSchema + * + * @return void + */ + function createSchema(Schema $createSchema); + + /** + * Updates the Schema to new schema version. + * + * @param \Doctrine\DBAL\Schema\Schema $toSchema + * @param boolean $noDrops + * + * @return void + */ + function updateSchema(Schema $toSchema, $noDrops = false); + + /** + * Drops the given database schema from the underlying db. + * + * @param \Doctrine\DBAL\Schema\Schema $dropSchema + * + * @return void + */ + function dropSchema(Schema $dropSchema); + + /** + * Drops all assets from the underlying db. + * + * @return void + */ + function dropAllSchema(); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SingleDatabaseSynchronizer.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SingleDatabaseSynchronizer.php new file mode 100644 index 00000000..7cab7a5b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SingleDatabaseSynchronizer.php @@ -0,0 +1,176 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Synchronizer; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Comparator; +use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector; + +/** + * Schema Synchronizer for Default DBAL Connection. + * + * @author Benjamin Eberlei + */ +class SingleDatabaseSynchronizer extends AbstractSchemaSynchronizer +{ + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $platform; + + /** + * @param \Doctrine\DBAL\Connection $conn + */ + public function __construct(Connection $conn) + { + parent::__construct($conn); + $this->platform = $conn->getDatabasePlatform(); + } + + /** + * {@inheritdoc} + */ + public function getCreateSchema(Schema $createSchema) + { + return $createSchema->toSql($this->platform); + } + + + /** + * {@inheritdoc} + */ + public function getUpdateSchema(Schema $toSchema, $noDrops = false) + { + $comparator = new Comparator(); + $sm = $this->conn->getSchemaManager(); + + $fromSchema = $sm->createSchema(); + $schemaDiff = $comparator->compare($fromSchema, $toSchema); + + if ($noDrops) { + return $schemaDiff->toSaveSql($this->platform); + } + + return $schemaDiff->toSql($this->platform); + } + + /** + * {@inheritdoc} + */ + public function getDropSchema(Schema $dropSchema) + { + $visitor = new DropSchemaSqlCollector($this->platform); + $sm = $this->conn->getSchemaManager(); + + $fullSchema = $sm->createSchema(); + + foreach ($fullSchema->getTables() as $table) { + if ($dropSchema->hasTable($table->getName())) { + $visitor->acceptTable($table); + } + + foreach ($table->getForeignKeys() as $foreignKey) { + if ( ! $dropSchema->hasTable($table->getName())) { + continue; + } + + if ( ! $dropSchema->hasTable($foreignKey->getForeignTableName())) { + continue; + } + + $visitor->acceptForeignKey($table, $foreignKey); + } + } + + if ( ! $this->platform->supportsSequences()) { + return $visitor->getQueries(); + } + + foreach ($dropSchema->getSequences() as $sequence) { + $visitor->acceptSequence($sequence); + } + + foreach ($dropSchema->getTables() as $table) { + if ( ! $table->hasPrimaryKey()) { + continue; + } + + $columns = $table->getPrimaryKey()->getColumns(); + if (count($columns) > 1) { + continue; + } + + $checkSequence = $table->getName() . "_" . $columns[0] . "_seq"; + if ($fullSchema->hasSequence($checkSequence)) { + $visitor->acceptSequence($fullSchema->getSequence($checkSequence)); + } + } + + return $visitor->getQueries(); + } + + /** + * {@inheritdoc} + */ + public function getDropAllSchema() + { + $sm = $this->conn->getSchemaManager(); + $visitor = new DropSchemaSqlCollector($this->platform); + + /* @var $schema \Doctrine\DBAL\Schema\Schema */ + $schema = $sm->createSchema(); + $schema->visit($visitor); + + return $visitor->getQueries(); + } + + /** + * {@inheritdoc} + */ + public function createSchema(Schema $createSchema) + { + $this->processSql($this->getCreateSchema($createSchema)); + } + + /** + * {@inheritdoc} + */ + public function updateSchema(Schema $toSchema, $noDrops = false) + { + $this->processSql($this->getUpdateSchema($toSchema, $noDrops)); + } + + /** + * {@inheritdoc} + */ + public function dropSchema(Schema $dropSchema) + { + $this->processSqlSafely($this->getDropSchema($dropSchema)); + } + + /** + * {@inheritdoc} + */ + public function dropAllSchema() + { + $this->processSql($this->getDropAllSchema()); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Table.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Table.php new file mode 100644 index 00000000..fb1e51da --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Table.php @@ -0,0 +1,850 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Schema\Visitor\Visitor; +use Doctrine\DBAL\DBALException; + +/** + * Object Representation of a table. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class Table extends AbstractAsset +{ + /** + * @var string + */ + protected $_name = null; + + /** + * @var Column[] + */ + protected $_columns = array(); + + /** + * @var Index[] + */ + private $implicitIndexes = array(); + + /** + * @var Index[] + */ + protected $_indexes = array(); + + /** + * @var string + */ + protected $_primaryKeyName = false; + + /** + * @var ForeignKeyConstraint[] + */ + protected $_fkConstraints = array(); + + /** + * @var array + */ + protected $_options = array(); + + /** + * @var SchemaConfig + */ + protected $_schemaConfig = null; + + /** + * @param string $tableName + * @param Column[] $columns + * @param Index[] $indexes + * @param ForeignKeyConstraint[] $fkConstraints + * @param integer $idGeneratorType + * @param array $options + * + * @throws DBALException + */ + public function __construct($tableName, array $columns=array(), array $indexes=array(), array $fkConstraints=array(), $idGeneratorType = 0, array $options=array()) + { + if (strlen($tableName) == 0) { + throw DBALException::invalidTableName($tableName); + } + + $this->_setName($tableName); + + foreach ($columns as $column) { + $this->_addColumn($column); + } + + foreach ($indexes as $idx) { + $this->_addIndex($idx); + } + + foreach ($fkConstraints as $constraint) { + $this->_addForeignKeyConstraint($constraint); + } + + $this->_options = $options; + } + + /** + * @param SchemaConfig $schemaConfig + * + * @return void + */ + public function setSchemaConfig(SchemaConfig $schemaConfig) + { + $this->_schemaConfig = $schemaConfig; + } + + /** + * @return integer + */ + protected function _getMaxIdentifierLength() + { + if ($this->_schemaConfig instanceof SchemaConfig) { + return $this->_schemaConfig->getMaxIdentifierLength(); + } else { + return 63; + } + } + + /** + * Sets the Primary Key. + * + * @param array $columns + * @param string|boolean $indexName + * + * @return self + */ + public function setPrimaryKey(array $columns, $indexName = false) + { + $this->_addIndex($this->_createIndex($columns, $indexName ?: "primary", true, true)); + + foreach ($columns as $columnName) { + $column = $this->getColumn($columnName); + $column->setNotnull(true); + } + + return $this; + } + + /** + * @param array $columnNames + * @param string|null $indexName + * @param array $flags + * @param array $options + * + * @return self + */ + public function addIndex(array $columnNames, $indexName = null, array $flags = array(), array $options = array()) + { + if ($indexName == null) { + $indexName = $this->_generateIdentifierName( + array_merge(array($this->getName()), $columnNames), "idx", $this->_getMaxIdentifierLength() + ); + } + + return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options)); + } + + /** + * Drops the primary key from this table. + * + * @return void + */ + public function dropPrimaryKey() + { + $this->dropIndex($this->_primaryKeyName); + $this->_primaryKeyName = false; + } + + /** + * Drops an index from this table. + * + * @param string $indexName The index name. + * + * @return void + * + * @throws SchemaException If the index does not exist. + */ + public function dropIndex($indexName) + { + $indexName = $this->normalizeIdentifier($indexName); + if ( ! $this->hasIndex($indexName)) { + throw SchemaException::indexDoesNotExist($indexName, $this->_name); + } + unset($this->_indexes[$indexName]); + } + + /** + * @param array $columnNames + * @param string|null $indexName + * @param array $options + * + * @return self + */ + public function addUniqueIndex(array $columnNames, $indexName = null, array $options = array()) + { + if ($indexName === null) { + $indexName = $this->_generateIdentifierName( + array_merge(array($this->getName()), $columnNames), "uniq", $this->_getMaxIdentifierLength() + ); + } + + return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, array(), $options)); + } + + /** + * Renames an index. + * + * @param string $oldIndexName The name of the index to rename from. + * @param string|null $newIndexName The name of the index to rename to. + * If null is given, the index name will be auto-generated. + * + * @return self This table instance. + * + * @throws SchemaException if no index exists for the given current name + * or if an index with the given new name already exists on this table. + */ + public function renameIndex($oldIndexName, $newIndexName = null) + { + $oldIndexName = $this->normalizeIdentifier($oldIndexName); + $normalizedNewIndexName = $this->normalizeIdentifier($newIndexName); + + if ($oldIndexName === $normalizedNewIndexName) { + return $this; + } + + if ( ! $this->hasIndex($oldIndexName)) { + throw SchemaException::indexDoesNotExist($oldIndexName, $this->_name); + } + + if ($this->hasIndex($normalizedNewIndexName)) { + throw SchemaException::indexAlreadyExists($normalizedNewIndexName, $this->_name); + } + + $oldIndex = $this->_indexes[$oldIndexName]; + + if ($oldIndex->isPrimary()) { + $this->dropPrimaryKey(); + + return $this->setPrimaryKey($oldIndex->getColumns(), $newIndexName); + } + + unset($this->_indexes[$oldIndexName]); + + if ($oldIndex->isUnique()) { + return $this->addUniqueIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getOptions()); + } + + return $this->addIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getFlags(), $oldIndex->getOptions()); + } + + /** + * Checks if an index begins in the order of the given columns. + * + * @param array $columnsNames + * + * @return boolean + */ + public function columnsAreIndexed(array $columnsNames) + { + foreach ($this->getIndexes() as $index) { + /* @var $index Index */ + if ($index->spansColumns($columnsNames)) { + return true; + } + } + + return false; + } + + /** + * @param array $columnNames + * @param string $indexName + * @param boolean $isUnique + * @param boolean $isPrimary + * @param array $flags + * @param array $options + * + * @return Index + * + * @throws SchemaException + */ + private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = array(), array $options = array()) + { + if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) { + throw SchemaException::indexNameInvalid($indexName); + } + + foreach ($columnNames as $columnName => $indexColOptions) { + if (is_numeric($columnName) && is_string($indexColOptions)) { + $columnName = $indexColOptions; + } + + if ( ! $this->hasColumn($columnName)) { + throw SchemaException::columnDoesNotExist($columnName, $this->_name); + } + } + + return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options); + } + + /** + * @param string $columnName + * @param string $typeName + * @param array $options + * + * @return Column + */ + public function addColumn($columnName, $typeName, array $options=array()) + { + $column = new Column($columnName, Type::getType($typeName), $options); + + $this->_addColumn($column); + + return $column; + } + + /** + * Renames a Column. + * + * @param string $oldColumnName + * @param string $newColumnName + * + * @return self + * + * @throws DBALException + */ + public function renameColumn($oldColumnName, $newColumnName) + { + throw new DBALException("Table#renameColumn() was removed, because it drops and recreates " . + "the column instead. There is no fix available, because a schema diff cannot reliably detect if a " . + "column was renamed or one column was created and another one dropped."); + } + + /** + * Change Column Details. + * + * @param string $columnName + * @param array $options + * + * @return self + */ + public function changeColumn($columnName, array $options) + { + $column = $this->getColumn($columnName); + $column->setOptions($options); + + return $this; + } + + /** + * Drops a Column from the Table. + * + * @param string $columnName + * + * @return self + */ + public function dropColumn($columnName) + { + $columnName = $this->normalizeIdentifier($columnName); + unset($this->_columns[$columnName]); + + return $this; + } + + /** + * Adds a foreign key constraint. + * + * Name is inferred from the local columns. + * + * @param Table|string $foreignTable Table schema instance or table name + * @param array $localColumnNames + * @param array $foreignColumnNames + * @param array $options + * @param string|null $constraintName + * + * @return self + */ + public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array(), $constraintName = null) + { + $constraintName = $constraintName ?: $this->_generateIdentifierName(array_merge((array) $this->getName(), $localColumnNames), "fk", $this->_getMaxIdentifierLength()); + + return $this->addNamedForeignKeyConstraint($constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options); + } + + /** + * Adds a foreign key constraint. + * + * Name is to be generated by the database itself. + * + * @deprecated Use {@link addForeignKeyConstraint} + * + * @param Table|string $foreignTable Table schema instance or table name + * @param array $localColumnNames + * @param array $foreignColumnNames + * @param array $options + * + * @return self + */ + public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array()) + { + return $this->addForeignKeyConstraint($foreignTable, $localColumnNames, $foreignColumnNames, $options); + } + + /** + * Adds a foreign key constraint with a given name. + * + * @deprecated Use {@link addForeignKeyConstraint} + * + * @param string $name + * @param Table|string $foreignTable Table schema instance or table name + * @param array $localColumnNames + * @param array $foreignColumnNames + * @param array $options + * + * @return self + * + * @throws SchemaException + */ + public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array()) + { + if ($foreignTable instanceof Table) { + foreach ($foreignColumnNames as $columnName) { + if ( ! $foreignTable->hasColumn($columnName)) { + throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName()); + } + } + } + + foreach ($localColumnNames as $columnName) { + if ( ! $this->hasColumn($columnName)) { + throw SchemaException::columnDoesNotExist($columnName, $this->_name); + } + } + + $constraint = new ForeignKeyConstraint( + $localColumnNames, $foreignTable, $foreignColumnNames, $name, $options + ); + $this->_addForeignKeyConstraint($constraint); + + return $this; + } + + /** + * @param string $name + * @param string $value + * + * @return self + */ + public function addOption($name, $value) + { + $this->_options[$name] = $value; + + return $this; + } + + /** + * @param Column $column + * + * @return void + * + * @throws SchemaException + */ + protected function _addColumn(Column $column) + { + $columnName = $column->getName(); + $columnName = $this->normalizeIdentifier($columnName); + + if (isset($this->_columns[$columnName])) { + throw SchemaException::columnAlreadyExists($this->getName(), $columnName); + } + + $this->_columns[$columnName] = $column; + } + + /** + * Adds an index to the table. + * + * @param Index $indexCandidate + * + * @return self + * + * @throws SchemaException + */ + protected function _addIndex(Index $indexCandidate) + { + $indexName = $indexCandidate->getName(); + $indexName = $this->normalizeIdentifier($indexName); + $replacedImplicitIndexes = array(); + + foreach ($this->implicitIndexes as $name => $implicitIndex) { + if ($implicitIndex->isFullfilledBy($indexCandidate) && isset($this->_indexes[$name])) { + $replacedImplicitIndexes[] = $name; + } + } + + if ((isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) || + ($this->_primaryKeyName != false && $indexCandidate->isPrimary()) + ) { + throw SchemaException::indexAlreadyExists($indexName, $this->_name); + } + + foreach ($replacedImplicitIndexes as $name) { + unset($this->_indexes[$name], $this->implicitIndexes[$name]); + } + + if ($indexCandidate->isPrimary()) { + $this->_primaryKeyName = $indexName; + } + + $this->_indexes[$indexName] = $indexCandidate; + + return $this; + } + + /** + * @param ForeignKeyConstraint $constraint + * + * @return void + */ + protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint) + { + $constraint->setLocalTable($this); + + if (strlen($constraint->getName())) { + $name = $constraint->getName(); + } else { + $name = $this->_generateIdentifierName( + array_merge((array) $this->getName(), $constraint->getLocalColumns()), "fk", $this->_getMaxIdentifierLength() + ); + } + $name = $this->normalizeIdentifier($name); + + $this->_fkConstraints[$name] = $constraint; + + // add an explicit index on the foreign key columns. If there is already an index that fulfils this requirements drop the request. + // In the case of __construct calling this method during hydration from schema-details all the explicitly added indexes + // lead to duplicates. This creates computation overhead in this case, however no duplicate indexes are ever added (based on columns). + $indexName = $this->_generateIdentifierName( + array_merge(array($this->getName()), $constraint->getColumns()), + "idx", + $this->_getMaxIdentifierLength() + ); + $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false); + + foreach ($this->_indexes as $existingIndex) { + if ($indexCandidate->isFullfilledBy($existingIndex)) { + return; + } + } + + $this->_addIndex($indexCandidate); + $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate; + } + + /** + * Returns whether this table has a foreign key constraint with the given name. + * + * @param string $constraintName + * + * @return boolean + */ + public function hasForeignKey($constraintName) + { + $constraintName = $this->normalizeIdentifier($constraintName); + + return isset($this->_fkConstraints[$constraintName]); + } + + /** + * Returns the foreign key constraint with the given name. + * + * @param string $constraintName The constraint name. + * + * @return ForeignKeyConstraint + * + * @throws SchemaException If the foreign key does not exist. + */ + public function getForeignKey($constraintName) + { + $constraintName = $this->normalizeIdentifier($constraintName); + if (!$this->hasForeignKey($constraintName)) { + throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name); + } + + return $this->_fkConstraints[$constraintName]; + } + + /** + * Removes the foreign key constraint with the given name. + * + * @param string $constraintName The constraint name. + * + * @return void + * + * @throws SchemaException + */ + public function removeForeignKey($constraintName) + { + $constraintName = $this->normalizeIdentifier($constraintName); + if (!$this->hasForeignKey($constraintName)) { + throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name); + } + + unset($this->_fkConstraints[$constraintName]); + } + + /** + * @return Column[] + */ + public function getColumns() + { + $columns = $this->_columns; + + $pkCols = array(); + $fkCols = array(); + + if ($this->hasPrimaryKey()) { + $pkCols = $this->getPrimaryKey()->getColumns(); + } + foreach ($this->getForeignKeys() as $fk) { + /* @var $fk ForeignKeyConstraint */ + $fkCols = array_merge($fkCols, $fk->getColumns()); + } + $colNames = array_unique(array_merge($pkCols, $fkCols, array_keys($columns))); + + uksort($columns, function ($a, $b) use ($colNames) { + return (array_search($a, $colNames) >= array_search($b, $colNames)); + }); + + return $columns; + } + + /** + * Returns whether this table has a Column with the given name. + * + * @param string $columnName The column name. + * + * @return boolean + */ + public function hasColumn($columnName) + { + $columnName = $this->normalizeIdentifier($columnName); + + return isset($this->_columns[$columnName]); + } + + /** + * Returns the Column with the given name. + * + * @param string $columnName The column name. + * + * @return Column + * + * @throws SchemaException If the column does not exist. + */ + public function getColumn($columnName) + { + $columnName = $this->normalizeIdentifier($columnName); + if ( ! $this->hasColumn($columnName)) { + throw SchemaException::columnDoesNotExist($columnName, $this->_name); + } + + return $this->_columns[$columnName]; + } + + /** + * Returns the primary key. + * + * @return Index|null The primary key, or null if this Table has no primary key. + */ + public function getPrimaryKey() + { + if ( ! $this->hasPrimaryKey()) { + return null; + } + + return $this->getIndex($this->_primaryKeyName); + } + + /** + * Returns the primary key columns. + * + * @return array + * + * @throws DBALException + */ + public function getPrimaryKeyColumns() + { + if ( ! $this->hasPrimaryKey()) { + throw new DBALException("Table " . $this->getName() . " has no primary key."); + } + + return $this->getPrimaryKey()->getColumns(); + } + + /** + * Returns whether this table has a primary key. + * + * @return boolean + */ + public function hasPrimaryKey() + { + return ($this->_primaryKeyName && $this->hasIndex($this->_primaryKeyName)); + } + + /** + * Returns whether this table has an Index with the given name. + * + * @param string $indexName The index name. + * + * @return boolean + */ + public function hasIndex($indexName) + { + $indexName = $this->normalizeIdentifier($indexName); + + return (isset($this->_indexes[$indexName])); + } + + /** + * Returns the Index with the given name. + * + * @param string $indexName The index name. + * + * @return Index + * + * @throws SchemaException If the index does not exist. + */ + public function getIndex($indexName) + { + $indexName = $this->normalizeIdentifier($indexName); + if ( ! $this->hasIndex($indexName)) { + throw SchemaException::indexDoesNotExist($indexName, $this->_name); + } + + return $this->_indexes[$indexName]; + } + + /** + * @return Index[] + */ + public function getIndexes() + { + return $this->_indexes; + } + + /** + * Returns the foreign key constraints. + * + * @return ForeignKeyConstraint[] + */ + public function getForeignKeys() + { + return $this->_fkConstraints; + } + + /** + * @param string $name + * + * @return boolean + */ + public function hasOption($name) + { + return isset($this->_options[$name]); + } + + /** + * @param string $name + * + * @return mixed + */ + public function getOption($name) + { + return $this->_options[$name]; + } + + /** + * @return array + */ + public function getOptions() + { + return $this->_options; + } + + /** + * @param Visitor $visitor + * + * @return void + */ + public function visit(Visitor $visitor) + { + $visitor->acceptTable($this); + + foreach ($this->getColumns() as $column) { + $visitor->acceptColumn($this, $column); + } + + foreach ($this->getIndexes() as $index) { + $visitor->acceptIndex($this, $index); + } + + foreach ($this->getForeignKeys() as $constraint) { + $visitor->acceptForeignKey($this, $constraint); + } + } + + /** + * Clone of a Table triggers a deep clone of all affected assets. + * + * @return void + */ + public function __clone() + { + foreach ($this->_columns as $k => $column) { + $this->_columns[$k] = clone $column; + } + foreach ($this->_indexes as $k => $index) { + $this->_indexes[$k] = clone $index; + } + foreach ($this->_fkConstraints as $k => $fk) { + $this->_fkConstraints[$k] = clone $fk; + $this->_fkConstraints[$k]->setLocalTable($this); + } + } + + /** + * Normalizes a given identifier. + * + * Trims quotes and lowercases the given identifier. + * + * @param string $identifier The identifier to normalize. + * + * @return string The normalized identifier. + */ + private function normalizeIdentifier($identifier) + { + return $this->trimQuotes(strtolower($identifier)); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/TableDiff.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/TableDiff.php new file mode 100644 index 00000000..a837fab6 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/TableDiff.php @@ -0,0 +1,170 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Table Diff. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class TableDiff +{ + /** + * @var string + */ + public $name = null; + + /** + * @var string|boolean + */ + public $newName = false; + + /** + * All added fields. + * + * @var \Doctrine\DBAL\Schema\Column[] + */ + public $addedColumns; + + /** + * All changed fields. + * + * @var \Doctrine\DBAL\Schema\ColumnDiff[] + */ + public $changedColumns = array(); + + /** + * All removed fields. + * + * @var \Doctrine\DBAL\Schema\Column[] + */ + public $removedColumns = array(); + + /** + * Columns that are only renamed from key to column instance name. + * + * @var \Doctrine\DBAL\Schema\Column[] + */ + public $renamedColumns = array(); + + /** + * All added indexes. + * + * @var \Doctrine\DBAL\Schema\Index[] + */ + public $addedIndexes = array(); + + /** + * All changed indexes. + * + * @var \Doctrine\DBAL\Schema\Index[] + */ + public $changedIndexes = array(); + + /** + * All removed indexes + * + * @var \Doctrine\DBAL\Schema\Index[] + */ + public $removedIndexes = array(); + + /** + * Indexes that are only renamed but are identical otherwise. + * + * @var \Doctrine\DBAL\Schema\Index[] + */ + public $renamedIndexes = array(); + + /** + * All added foreign key definitions + * + * @var \Doctrine\DBAL\Schema\ForeignKeyConstraint[] + */ + public $addedForeignKeys = array(); + + /** + * All changed foreign keys + * + * @var \Doctrine\DBAL\Schema\ForeignKeyConstraint[] + */ + public $changedForeignKeys = array(); + + /** + * All removed foreign keys + * + * @var \Doctrine\DBAL\Schema\ForeignKeyConstraint[] + */ + public $removedForeignKeys = array(); + + /** + * @var \Doctrine\DBAL\Schema\Table + */ + public $fromTable; + + /** + * Constructs an TableDiff object. + * + * @param string $tableName + * @param \Doctrine\DBAL\Schema\Column[] $addedColumns + * @param \Doctrine\DBAL\Schema\ColumnDiff[] $changedColumns + * @param \Doctrine\DBAL\Schema\Column[] $removedColumns + * @param \Doctrine\DBAL\Schema\Index[] $addedIndexes + * @param \Doctrine\DBAL\Schema\Index[] $changedIndexes + * @param \Doctrine\DBAL\Schema\Index[] $removedIndexes + * @param \Doctrine\DBAL\Schema\Table|null $fromTable + */ + public function __construct($tableName, $addedColumns = array(), + $changedColumns = array(), $removedColumns = array(), $addedIndexes = array(), + $changedIndexes = array(), $removedIndexes = array(), Table $fromTable = null) + { + $this->name = $tableName; + $this->addedColumns = $addedColumns; + $this->changedColumns = $changedColumns; + $this->removedColumns = $removedColumns; + $this->addedIndexes = $addedIndexes; + $this->changedIndexes = $changedIndexes; + $this->removedIndexes = $removedIndexes; + $this->fromTable = $fromTable; + } + + /** + * @param AbstractPlatform $platform The platform to use for retrieving this table diff's name. + * + * @return \Doctrine\DBAL\Schema\Identifier + */ + public function getName(AbstractPlatform $platform) + { + return new Identifier( + $this->fromTable instanceof Table ? $this->fromTable->getQuotedName($platform) : $this->name + ); + } + + /** + * @return \Doctrine\DBAL\Schema\Identifier|boolean + */ + public function getNewName() + { + return $this->newName ? new Identifier($this->newName) : $this->newName; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/View.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/View.php new file mode 100644 index 00000000..0ef7d305 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/View.php @@ -0,0 +1,53 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * Representation of a Database View. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Benjamin Eberlei + */ +class View extends AbstractAsset +{ + /** + * @var string + */ + private $_sql; + + /** + * @param string $name + * @param string $sql + */ + public function __construct($name, $sql) + { + $this->_setName($name); + $this->_sql = $sql; + } + + /** + * @return string + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/AbstractVisitor.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/AbstractVisitor.php new file mode 100644 index 00000000..7bf5b2fe --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/AbstractVisitor.php @@ -0,0 +1,85 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Visitor; + +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\Index; + +/** + * Abstract Visitor with empty methods for easy extension. + */ +class AbstractVisitor implements Visitor, NamespaceVisitor +{ + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + */ + public function acceptSchema(Schema $schema) + { + } + + /** + * {@inheritdoc} + */ + public function acceptNamespace($namespaceName) + { + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + */ + public function acceptTable(Table $table) + { + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \Doctrine\DBAL\Schema\Column $column + */ + public function acceptColumn(Table $table, Column $column) + { + } + + /** + * @param \Doctrine\DBAL\Schema\Table $localTable + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $fkConstraint + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \Doctrine\DBAL\Schema\Index $index + */ + public function acceptIndex(Table $table, Index $index) + { + } + + /** + * @param \Doctrine\DBAL\Schema\Sequence $sequence + */ + public function acceptSequence(Sequence $sequence) + { + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/CreateSchemaSqlCollector.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/CreateSchemaSqlCollector.php new file mode 100644 index 00000000..5e0c6ed4 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/CreateSchemaSqlCollector.php @@ -0,0 +1,148 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Visitor; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; + +class CreateSchemaSqlCollector extends AbstractVisitor +{ + /** + * @var array + */ + private $createNamespaceQueries = array(); + + /** + * @var array + */ + private $createTableQueries = array(); + + /** + * @var array + */ + private $createSequenceQueries = array(); + + /** + * @var array + */ + private $createFkConstraintQueries = array(); + + /** + * + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $platform = null; + + /** + * @param AbstractPlatform $platform + */ + public function __construct(AbstractPlatform $platform) + { + $this->platform = $platform; + } + + /** + * {@inheritdoc} + */ + public function acceptNamespace($namespaceName) + { + if ($this->platform->supportsSchemas()) { + $this->createNamespaceQueries = array_merge( + $this->createNamespaceQueries, + (array) $this->platform->getCreateSchemaSQL($namespaceName) + ); + } + } + + /** + * {@inheritdoc} + */ + public function acceptTable(Table $table) + { + $this->createTableQueries = array_merge($this->createTableQueries, (array) $this->platform->getCreateTableSQL($table)); + } + + /** + * {@inheritdoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + if ($this->platform->supportsForeignKeyConstraints()) { + $this->createFkConstraintQueries = array_merge( + $this->createFkConstraintQueries, + (array) $this->platform->getCreateForeignKeySQL( + $fkConstraint, $localTable + ) + ); + } + } + + /** + * {@inheritdoc} + */ + public function acceptSequence(Sequence $sequence) + { + $this->createSequenceQueries = array_merge( + $this->createSequenceQueries, + (array) $this->platform->getCreateSequenceSQL($sequence) + ); + } + + /** + * @return void + */ + public function resetQueries() + { + $this->createNamespaceQueries = array(); + $this->createTableQueries = array(); + $this->createSequenceQueries = array(); + $this->createFkConstraintQueries = array(); + } + + /** + * Gets all queries collected so far. + * + * @return array + */ + public function getQueries() + { + $sql = array(); + + foreach ($this->createNamespaceQueries as $schemaSql) { + $sql = array_merge($sql, (array) $schemaSql); + } + + foreach ($this->createTableQueries as $schemaSql) { + $sql = array_merge($sql, (array) $schemaSql); + } + + foreach ($this->createSequenceQueries as $schemaSql) { + $sql = array_merge($sql, (array) $schemaSql); + } + + foreach ($this->createFkConstraintQueries as $schemaSql) { + $sql = array_merge($sql, (array) $schemaSql); + } + + return $sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/DropSchemaSqlCollector.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/DropSchemaSqlCollector.php new file mode 100644 index 00000000..ab9752d5 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/DropSchemaSqlCollector.php @@ -0,0 +1,126 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Visitor; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\SchemaException; + +/** + * Gathers SQL statements that allow to completely drop the current schema. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class DropSchemaSqlCollector extends AbstractVisitor +{ + /** + * @var \SplObjectStorage + */ + private $constraints; + + /** + * @var \SplObjectStorage + */ + private $sequences; + + /** + * @var \SplObjectStorage + */ + private $tables; + + /** + * @var AbstractPlatform + */ + private $platform; + + /** + * @param AbstractPlatform $platform + */ + public function __construct(AbstractPlatform $platform) + { + $this->platform = $platform; + $this->clearQueries(); + } + + /** + * {@inheritdoc} + */ + public function acceptTable(Table $table) + { + $this->tables->attach($table); + } + + /** + * {@inheritdoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + if (strlen($fkConstraint->getName()) == 0) { + throw SchemaException::namedForeignKeyRequired($localTable, $fkConstraint); + } + + $this->constraints->attach($fkConstraint, $localTable); + } + + /** + * {@inheritdoc} + */ + public function acceptSequence(Sequence $sequence) + { + $this->sequences->attach($sequence); + } + + /** + * @return void + */ + public function clearQueries() + { + $this->constraints = new \SplObjectStorage(); + $this->sequences = new \SplObjectStorage(); + $this->tables = new \SplObjectStorage(); + } + + /** + * @return array + */ + public function getQueries() + { + $sql = array(); + + foreach ($this->constraints as $fkConstraint) { + $localTable = $this->constraints[$fkConstraint]; + $sql[] = $this->platform->getDropForeignKeySQL($fkConstraint, $localTable); + } + + foreach ($this->sequences as $sequence) { + $sql[] = $this->platform->getDropSequenceSQL($sequence); + } + + foreach ($this->tables as $table) { + $sql[] = $this->platform->getDropTableSQL($table); + } + + return $sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Graphviz.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Graphviz.php new file mode 100644 index 00000000..1573ddf5 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Graphviz.php @@ -0,0 +1,174 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Visitor; + +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; + +/** + * Create a Graphviz output of a Schema. + */ +class Graphviz extends AbstractVisitor +{ + /** + * @var string + */ + private $output = ''; + + /** + * {@inheritdoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + $this->output .= $this->createNodeRelation( + $fkConstraint->getLocalTableName() . ":col" . current($fkConstraint->getLocalColumns()).":se", + $fkConstraint->getForeignTableName() . ":col" . current($fkConstraint->getForeignColumns()).":se", + array( + 'dir' => 'back', + 'arrowtail' => 'dot', + 'arrowhead' => 'normal', + ) + ); + } + + /** + * {@inheritdoc} + */ + public function acceptSchema(Schema $schema) + { + $this->output = 'digraph "' . sha1(mt_rand()) . '" {' . "\n"; + $this->output .= 'splines = true;' . "\n"; + $this->output .= 'overlap = false;' . "\n"; + $this->output .= 'outputorder=edgesfirst;'."\n"; + $this->output .= 'mindist = 0.6;' . "\n"; + $this->output .= 'sep = .2;' . "\n"; + } + + /** + * {@inheritdoc} + */ + public function acceptTable(Table $table) + { + $this->output .= $this->createNode( + $table->getName(), + array( + 'label' => $this->createTableLabel($table), + 'shape' => 'plaintext', + ) + ); + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * + * @return string + */ + private function createTableLabel(Table $table) + { + // Start the table + $label = '<'; + + // The title + $label .= ''; + + // The attributes block + foreach ($table->getColumns() as $column) { + $columnLabel = $column->getName(); + + $label .= ''; + $label .= ''; + $label .= ''; + } + + // End the table + $label .= '
' . $table->getName() . '
'; + $label .= '' . $columnLabel . ''; + $label .= '' . strtolower($column->getType()) . ''; + if ($table->hasPrimaryKey() && in_array($column->getName(), $table->getPrimaryKey()->getColumns())) { + $label .= "\xe2\x9c\xb7"; + } + $label .= '
>'; + + return $label; + } + + /** + * @param string $name + * @param array $options + * + * @return string + */ + private function createNode($name, $options) + { + $node = $name . " ["; + foreach ($options as $key => $value) { + $node .= $key . '=' . $value . ' '; + } + $node .= "]\n"; + + return $node; + } + + /** + * @param string $node1 + * @param string $node2 + * @param array $options + * + * @return string + */ + private function createNodeRelation($node1, $node2, $options) + { + $relation = $node1 . ' -> ' . $node2 . ' ['; + foreach ($options as $key => $value) { + $relation .= $key . '=' . $value . ' '; + } + $relation .= "]\n"; + + return $relation; + } + + /** + * Get Graphviz Output + * + * @return string + */ + public function getOutput() + { + return $this->output . "}"; + } + + /** + * Writes dot language output to a file. This should usually be a *.dot file. + * + * You have to convert the output into a viewable format. For example use "neato" on linux systems + * and execute: + * + * neato -Tpng -o er.png er.dot + * + * @param string $filename + * + * @return void + */ + public function write($filename) + { + file_put_contents($filename, $this->getOutput()); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/NamespaceVisitor.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/NamespaceVisitor.php new file mode 100644 index 00000000..da7b1edb --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/NamespaceVisitor.php @@ -0,0 +1,37 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Visitor; + +/** + * Visitor that can visit schema namespaces. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +interface NamespaceVisitor +{ + /** + * Accepts a schema namespace name. + * + * @param string $namespaceName The schema namespace name to accept. + */ + public function acceptNamespace($namespaceName); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/RemoveNamespacedAssets.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/RemoveNamespacedAssets.php new file mode 100644 index 00000000..e328d071 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/RemoveNamespacedAssets.php @@ -0,0 +1,94 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Visitor; + +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; + +/** + * Removes assets from a schema that are not in the default namespace. + * + * Some databases such as MySQL support cross databases joins, but don't + * allow to call DDLs to a database from another connected database. + * Before a schema is serialized into SQL this visitor can cleanup schemas with + * non default namespaces. + * + * This visitor filters all these non-default namespaced tables and sequences + * and removes them from the SChema instance. + * + * @author Benjamin Eberlei + * @since 2.2 + */ +class RemoveNamespacedAssets extends AbstractVisitor +{ + /** + * @var \Doctrine\DBAL\Schema\Schema + */ + private $schema; + + /** + * {@inheritdoc} + */ + public function acceptSchema(Schema $schema) + { + $this->schema = $schema; + } + + /** + * {@inheritdoc} + */ + public function acceptTable(Table $table) + { + if ( ! $table->isInDefaultNamespace($this->schema->getName())) { + $this->schema->dropTable($table->getName()); + } + } + + /** + * {@inheritdoc} + */ + public function acceptSequence(Sequence $sequence) + { + if ( ! $sequence->isInDefaultNamespace($this->schema->getName())) { + $this->schema->dropSequence($sequence->getName()); + } + } + + /** + * {@inheritdoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + // The table may already be deleted in a previous + // RemoveNamespacedAssets#acceptTable call. Removing Foreign keys that + // point to nowhere. + if ( ! $this->schema->hasTable($fkConstraint->getForeignTableName())) { + $localTable->removeForeignKey($fkConstraint->getName()); + return; + } + + $foreignTable = $this->schema->getTable($fkConstraint->getForeignTableName()); + if ( ! $foreignTable->isInDefaultNamespace($this->schema->getName())) { + $localTable->removeForeignKey($fkConstraint->getName()); + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/SchemaDiffVisitor.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/SchemaDiffVisitor.php new file mode 100644 index 00000000..bde4453b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/SchemaDiffVisitor.php @@ -0,0 +1,82 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Visitor; + +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; + +/** + * Visit a SchemaDiff. + * + * @link www.doctrine-project.org + * @since 2.4 + * @author Benjamin Eberlei + */ +interface SchemaDiffVisitor +{ + /** + * Visit an orphaned foreign key whose table was deleted. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey + */ + function visitOrphanedForeignKey(ForeignKeyConstraint $foreignKey); + + /** + * Visit a sequence that has changed. + * + * @param \Doctrine\DBAL\Schema\Sequence $sequence + */ + function visitChangedSequence(Sequence $sequence); + + /** + * Visit a sequence that has been removed. + * + * @param \Doctrine\DBAL\Schema\Sequence $sequence + */ + function visitRemovedSequence(Sequence $sequence); + + /** + * @param \Doctrine\DBAL\Schema\Sequence $sequence + */ + function visitNewSequence(Sequence $sequence); + + /** + * @param \Doctrine\DBAL\Schema\Table $table + */ + function visitNewTable(Table $table); + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey + */ + function visitNewTableForeignKey(Table $table, ForeignKeyConstraint $foreignKey); + + /** + * @param \Doctrine\DBAL\Schema\Table $table + */ + function visitRemovedTable(Table $table); + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + */ + function visitChangedTable(TableDiff $tableDiff); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Visitor.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Visitor.php new file mode 100644 index 00000000..656f0e20 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Visitor.php @@ -0,0 +1,82 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Visitor; + +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\Index; + +/** + * Schema Visitor used for Validation or Generation purposes. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +interface Visitor +{ + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + * + * @return void + */ + public function acceptSchema(Schema $schema); + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * + * @return void + */ + public function acceptTable(Table $table); + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \Doctrine\DBAL\Schema\Column $column + * + * @return void + */ + public function acceptColumn(Table $table, Column $column); + + /** + * @param \Doctrine\DBAL\Schema\Table $localTable + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $fkConstraint + * + * @return void + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint); + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \Doctrine\DBAL\Schema\Index $index + * + * @return void + */ + public function acceptIndex(Table $table, Index $index); + + /** + * @param \Doctrine\DBAL\Schema\Sequence $sequence + * + * @return void + */ + public function acceptSequence(Sequence $sequence); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardConnection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardConnection.php new file mode 100644 index 00000000..c5fb7768 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardConnection.php @@ -0,0 +1,272 @@ +. + */ + +namespace Doctrine\DBAL\Sharding; + +use Doctrine\Common\EventManager; +use Doctrine\DBAL\Configuration; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Event\ConnectionEventArgs; +use Doctrine\DBAL\Events; +use Doctrine\DBAL\Sharding\ShardChoser\ShardChoser; + +/** + * Sharding implementation that pools many different connections + * internally and serves data from the currently active connection. + * + * The internals of this class are: + * + * - All sharding clients are specified and given a shard-id during + * configuration. + * - By default, the global shard is selected. If no global shard is configured + * an exception is thrown on access. + * - Selecting a shard by distribution value delegates the mapping + * "distributionValue" => "client" to the ShardChooser interface. + * - An exception is thrown if trying to switch shards during an open + * transaction. + * + * Instantiation through the DriverManager looks like: + * + * @example + * + * $conn = DriverManager::getConnection(array( + * 'wrapperClass' => 'Doctrine\DBAL\Sharding\PoolingShardConnection', + * 'driver' => 'pdo_mysql', + * 'global' => array('user' => '', 'password' => '', 'host' => '', 'dbname' => ''), + * 'shards' => array( + * array('id' => 1, 'user' => 'slave1', 'password', 'host' => '', 'dbname' => ''), + * array('id' => 2, 'user' => 'slave2', 'password', 'host' => '', 'dbname' => ''), + * ), + * 'shardChoser' => 'Doctrine\DBAL\Sharding\ShardChoser\MultiTenantShardChoser', + * )); + * $shardManager = $conn->getShardManager(); + * $shardManager->selectGlobal(); + * $shardManager->selectShard($value); + * + * @author Benjamin Eberlei + */ +class PoolingShardConnection extends Connection +{ + /** + * @var array + */ + private $activeConnections; + + /** + * @var integer + */ + private $activeShardId; + + /** + * @var array + */ + private $connections; + + /** + * @param array $params + * @param \Doctrine\DBAL\Driver $driver + * @param \Doctrine\DBAL\Configuration $config + * @param \Doctrine\Common\EventManager $eventManager + * + * @throws \InvalidArgumentException + */ + public function __construct(array $params, Driver $driver, Configuration $config = null, EventManager $eventManager = null) + { + if ( !isset($params['global']) || !isset($params['shards'])) { + throw new \InvalidArgumentException("Connection Parameters require 'global' and 'shards' configurations."); + } + + if ( !isset($params['shardChoser'])) { + throw new \InvalidArgumentException("Missing Shard Choser configuration 'shardChoser'"); + } + + if (is_string($params['shardChoser'])) { + $params['shardChoser'] = new $params['shardChoser']; + } + + if ( ! ($params['shardChoser'] instanceof ShardChoser)) { + throw new \InvalidArgumentException("The 'shardChoser' configuration is not a valid instance of Doctrine\DBAL\Sharding\ShardChoser\ShardChoser"); + } + + $this->connections[0] = array_merge($params, $params['global']); + + foreach ($params['shards'] as $shard) { + if ( ! isset($shard['id'])) { + throw new \InvalidArgumentException("Missing 'id' for one configured shard. Please specify a unique shard-id."); + } + + if ( !is_numeric($shard['id']) || $shard['id'] < 1) { + throw new \InvalidArgumentException("Shard Id has to be a non-negative number."); + } + + if (isset($this->connections[$shard['id']])) { + throw new \InvalidArgumentException("Shard " . $shard['id'] . " is duplicated in the configuration."); + } + + $this->connections[$shard['id']] = array_merge($params, $shard); + } + + parent::__construct($params, $driver, $config, $eventManager); + } + + /** + * Get active shard id. + * + * @return integer + */ + public function getActiveShardId() + { + return $this->activeShardId; + } + + /** + * {@inheritdoc} + */ + public function getParams() + { + return $this->activeShardId ? $this->connections[$this->activeShardId] : $this->connections[0]; + } + + /** + * {@inheritdoc} + */ + public function getHost() + { + $params = $this->getParams(); + + return isset($params['host']) ? $params['host'] : parent::getHost(); + } + + /** + * {@inheritdoc} + */ + public function getPort() + { + $params = $this->getParams(); + + return isset($params['port']) ? $params['port'] : parent::getPort(); + } + + /** + * {@inheritdoc} + */ + public function getUsername() + { + $params = $this->getParams(); + + return isset($params['user']) ? $params['user'] : parent::getUsername(); + } + + /** + * {@inheritdoc} + */ + public function getPassword() + { + $params = $this->getParams(); + + return isset($params['password']) ? $params['password'] : parent::getPassword(); + } + + /** + * Connects to a given shard. + * + * @param mixed $shardId + * + * @return boolean + * + * @throws \Doctrine\DBAL\Sharding\ShardingException + */ + public function connect($shardId = null) + { + if ($shardId === null && $this->_conn) { + return false; + } + + if ($shardId !== null && $shardId === $this->activeShardId) { + return false; + } + + if ($this->getTransactionNestingLevel() > 0) { + throw new ShardingException("Cannot switch shard when transaction is active."); + } + + $this->activeShardId = (int)$shardId; + + if (isset($this->activeConnections[$this->activeShardId])) { + $this->_conn = $this->activeConnections[$this->activeShardId]; + + return false; + } + + $this->_conn = $this->activeConnections[$this->activeShardId] = $this->connectTo($this->activeShardId); + + if ($this->_eventManager->hasListeners(Events::postConnect)) { + $eventArgs = new ConnectionEventArgs($this); + $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); + } + + return true; + } + + /** + * Connects to a specific connection. + * + * @param string $shardId + * + * @return \Doctrine\DBAL\Driver\Connection + */ + protected function connectTo($shardId) + { + $params = $this->getParams(); + + $driverOptions = isset($params['driverOptions']) ? $params['driverOptions'] : array(); + + $connectionParams = $this->connections[$shardId]; + + $user = isset($connectionParams['user']) ? $connectionParams['user'] : null; + $password = isset($connectionParams['password']) ? $connectionParams['password'] : null; + + return $this->_driver->connect($connectionParams, $user, $password, $driverOptions); + } + + /** + * @param string|null $shardId + * + * @return boolean + */ + public function isConnected($shardId = null) + { + if ($shardId === null) { + return $this->_conn !== null; + } + + return isset($this->activeConnections[$shardId]); + } + + /** + * @return void + */ + public function close() + { + $this->_conn = null; + $this->activeConnections = null; + $this->activeShardId = null; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardManager.php new file mode 100644 index 00000000..64307ca6 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardManager.php @@ -0,0 +1,134 @@ +. + */ + +namespace Doctrine\DBAL\Sharding; + +use Doctrine\DBAL\Sharding\ShardChoser\ShardChoser; + +/** + * Shard Manager for the Connection Pooling Shard Strategy + * + * @author Benjamin Eberlei + */ +class PoolingShardManager implements ShardManager +{ + /** + * @var PoolingShardConnection + */ + private $conn; + + /** + * @var ShardChoser + */ + private $choser; + + /** + * @var string|null + */ + private $currentDistributionValue; + + /** + * @param PoolingShardConnection $conn + */ + public function __construct(PoolingShardConnection $conn) + { + $params = $conn->getParams(); + $this->conn = $conn; + $this->choser = $params['shardChoser']; + } + + /** + * @return void + */ + public function selectGlobal() + { + $this->conn->connect(0); + $this->currentDistributionValue = null; + } + + /** + * @param string $distributionValue + * + * @return void + */ + public function selectShard($distributionValue) + { + $shardId = $this->choser->pickShard($distributionValue, $this->conn); + $this->conn->connect($shardId); + $this->currentDistributionValue = $distributionValue; + } + + /** + * @return string|null + */ + public function getCurrentDistributionValue() + { + return $this->currentDistributionValue; + } + + /** + * @return array + */ + public function getShards() + { + $params = $this->conn->getParams(); + $shards = array(); + + foreach ($params['shards'] as $shard) { + $shards[] = array('id' => $shard['id']); + } + + return $shards; + } + + /** + * @param string $sql + * @param array $params + * @param array $types + * + * @return array + * + * @throws \RuntimeException + */ + public function queryAll($sql, array $params, array $types) + { + $shards = $this->getShards(); + if (!$shards) { + throw new \RuntimeException("No shards found."); + } + + $result = array(); + $oldDistribution = $this->getCurrentDistributionValue(); + + foreach ($shards as $shard) { + $this->conn->connect($shard['id']); + foreach ($this->conn->fetchAll($sql, $params, $types) as $row) { + $result[] = $row; + } + } + + if ($oldDistribution === null) { + $this->selectGlobal(); + } else { + $this->selectShard($oldDistribution); + } + + return $result; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureFederationsSynchronizer.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureFederationsSynchronizer.php new file mode 100644 index 00000000..edda8358 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureFederationsSynchronizer.php @@ -0,0 +1,298 @@ +. + */ + +namespace Doctrine\DBAL\Sharding\SQLAzure; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Types\Type; + +use Doctrine\DBAL\Schema\Synchronizer\AbstractSchemaSynchronizer; +use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer; +use Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer; + +/** + * SQL Azure Schema Synchronizer. + * + * Will iterate over all shards when performing schema operations. This is done + * by partitioning the passed schema into subschemas for the federation and the + * global database and then applying the operations step by step using the + * {@see \Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer}. + * + * @author Benjamin Eberlei + */ +class SQLAzureFederationsSynchronizer extends AbstractSchemaSynchronizer +{ + const FEDERATION_TABLE_FEDERATED = 'azure.federated'; + const FEDERATION_DISTRIBUTION_NAME = 'azure.federatedOnDistributionName'; + + /** + * @var \Doctrine\DBAL\Sharding\SQLAzure\SQLAzureShardManager + */ + private $shardManager; + + /** + * @var \Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer + */ + private $synchronizer; + + /** + * @param \Doctrine\DBAL\Connection $conn + * @param \Doctrine\DBAL\Sharding\SQLAzure\SQLAzureShardManager $shardManager + * @param \Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer|null $sync + */ + public function __construct(Connection $conn, SQLAzureShardManager $shardManager, SchemaSynchronizer $sync = null) + { + parent::__construct($conn); + $this->shardManager = $shardManager; + $this->synchronizer = $sync ?: new SingleDatabaseSynchronizer($conn); + } + + /** + * {@inheritdoc} + */ + public function getCreateSchema(Schema $createSchema) + { + $sql = array(); + + list($global, $federation) = $this->partitionSchema($createSchema); + + $globalSql = $this->synchronizer->getCreateSchema($global); + if ($globalSql) { + $sql[] = "-- Create Root Federation\n" . + "USE FEDERATION ROOT WITH RESET;"; + $sql = array_merge($sql, $globalSql); + } + + $federationSql = $this->synchronizer->getCreateSchema($federation); + + if ($federationSql) { + $defaultValue = $this->getFederationTypeDefaultValue(); + + $sql[] = $this->getCreateFederationStatement(); + $sql[] = "USE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " = " . $defaultValue . ") WITH RESET, FILTERING = OFF;"; + $sql = array_merge($sql, $federationSql); + } + + return $sql; + } + + /** + * {@inheritdoc} + */ + public function getUpdateSchema(Schema $toSchema, $noDrops = false) + { + return $this->work($toSchema, function ($synchronizer, $schema) use ($noDrops) { + return $synchronizer->getUpdateSchema($schema, $noDrops); + }); + } + + /** + * {@inheritdoc} + */ + public function getDropSchema(Schema $dropSchema) + { + return $this->work($dropSchema, function ($synchronizer, $schema) { + return $synchronizer->getDropSchema($schema); + }); + } + + /** + * {@inheritdoc} + */ + public function createSchema(Schema $createSchema) + { + $this->processSql($this->getCreateSchema($createSchema)); + } + + /** + * {@inheritdoc} + */ + public function updateSchema(Schema $toSchema, $noDrops = false) + { + $this->processSql($this->getUpdateSchema($toSchema, $noDrops)); + } + + /** + * {@inheritdoc} + */ + public function dropSchema(Schema $dropSchema) + { + $this->processSqlSafely($this->getDropSchema($dropSchema)); + } + + /** + * {@inheritdoc} + */ + public function getDropAllSchema() + { + $this->shardManager->selectGlobal(); + $globalSql = $this->synchronizer->getDropAllSchema(); + + if ($globalSql) { + $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;"; + $sql = array_merge($sql, $globalSql); + } + + $shards = $this->shardManager->getShards(); + foreach ($shards as $shard) { + $this->shardManager->selectShard($shard['rangeLow']); + + $federationSql = $this->synchronizer->getDropAllSchema(); + if ($federationSql) { + $sql[] = "-- Work on Federation ID " . $shard['id'] . "\n" . + "USE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " = " . $shard['rangeLow'].") WITH RESET, FILTERING = OFF;"; + $sql = array_merge($sql, $federationSql); + } + } + + $sql[] = "USE FEDERATION ROOT WITH RESET;"; + $sql[] = "DROP FEDERATION " . $this->shardManager->getFederationName(); + + return $sql; + } + + /** + * {@inheritdoc} + */ + public function dropAllSchema() + { + $this->processSqlSafely($this->getDropAllSchema()); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + * + * @return array + */ + private function partitionSchema(Schema $schema) + { + return array( + $this->extractSchemaFederation($schema, false), + $this->extractSchemaFederation($schema, true), + ); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + * @param boolean $isFederation + * + * @return \Doctrine\DBAL\Schema\Schema + * + * @throws \RuntimeException + */ + private function extractSchemaFederation(Schema $schema, $isFederation) + { + $partitionedSchema = clone $schema; + + foreach ($partitionedSchema->getTables() as $table) { + if ($isFederation) { + $table->addOption(self::FEDERATION_DISTRIBUTION_NAME, $this->shardManager->getDistributionKey()); + } + + if ($table->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) { + $partitionedSchema->dropTable($table->getName()); + } else { + foreach ($table->getForeignKeys() as $fk) { + $foreignTable = $schema->getTable($fk->getForeignTableName()); + if ($foreignTable->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) { + throw new \RuntimeException("Cannot have foreign key between global/federation."); + } + } + } + } + + return $partitionedSchema; + } + + /** + * Work on the Global/Federation based on currently existing shards and + * perform the given operation on the underlying schema synchronizer given + * the different partitioned schema instances. + * + * @param \Doctrine\DBAL\Schema\Schema $schema + * @param \Closure $operation + * + * @return array + */ + private function work(Schema $schema, \Closure $operation) + { + list($global, $federation) = $this->partitionSchema($schema); + $sql = array(); + + $this->shardManager->selectGlobal(); + $globalSql = $operation($this->synchronizer, $global); + + if ($globalSql) { + $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;"; + $sql = array_merge($sql, $globalSql); + } + + $shards = $this->shardManager->getShards(); + + foreach ($shards as $shard) { + $this->shardManager->selectShard($shard['rangeLow']); + + $federationSql = $operation($this->synchronizer, $federation); + if ($federationSql) { + $sql[] = "-- Work on Federation ID " . $shard['id'] . "\n" . + "USE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " = " . $shard['rangeLow'].") WITH RESET, FILTERING = OFF;"; + $sql = array_merge($sql, $federationSql); + } + } + + return $sql; + } + + /** + * @return string + */ + private function getFederationTypeDefaultValue() + { + $federationType = Type::getType($this->shardManager->getDistributionType()); + + switch ($federationType->getName()) { + case Type::GUID: + $defaultValue = '00000000-0000-0000-0000-000000000000'; + break; + case Type::INTEGER: + case Type::SMALLINT: + case Type::BIGINT: + $defaultValue = '0'; + break; + default: + $defaultValue = ''; + break; + } + + return $defaultValue; + } + + /** + * @return string + */ + private function getCreateFederationStatement() + { + $federationType = Type::getType($this->shardManager->getDistributionType()); + $federationTypeSql = $federationType->getSqlDeclaration(array(), $this->conn->getDatabasePlatform()); + + return "--Create Federation\n" . + "CREATE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " " . $federationTypeSql ." RANGE)"; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureShardManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureShardManager.php new file mode 100644 index 00000000..b9047a95 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureShardManager.php @@ -0,0 +1,243 @@ +. + */ + +namespace Doctrine\DBAL\Sharding\SQLAzure; + +use Doctrine\DBAL\Sharding\ShardManager; +use Doctrine\DBAL\Sharding\ShardingException; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Types\Type; + +/** + * Sharding using the SQL Azure Federations support. + * + * @author Benjamin Eberlei + */ +class SQLAzureShardManager implements ShardManager +{ + /** + * @var string + */ + private $federationName; + + /** + * @var boolean + */ + private $filteringEnabled; + + /** + * @var string + */ + private $distributionKey; + + /** + * @var string + */ + private $distributionType; + + /** + * @var \Doctrine\DBAL\Connection + */ + private $conn; + + /** + * @var string + */ + private $currentDistributionValue; + + /** + * @param \Doctrine\DBAL\Connection $conn + * + * @throws \Doctrine\DBAL\Sharding\ShardingException + */ + public function __construct(Connection $conn) + { + $this->conn = $conn; + $params = $conn->getParams(); + + if ( ! isset($params['sharding']['federationName'])) { + throw ShardingException::missingDefaultFederationName(); + } + + if ( ! isset($params['sharding']['distributionKey'])) { + throw ShardingException::missingDefaultDistributionKey(); + } + + if ( ! isset($params['sharding']['distributionType'])) { + throw ShardingException::missingDistributionType(); + } + + $this->federationName = $params['sharding']['federationName']; + $this->distributionKey = $params['sharding']['distributionKey']; + $this->distributionType = $params['sharding']['distributionType']; + $this->filteringEnabled = (isset($params['sharding']['filteringEnabled'])) ? (bool) $params['sharding']['filteringEnabled'] : false; + } + + /** + * Gets the name of the federation. + * + * @return string + */ + public function getFederationName() + { + return $this->federationName; + } + + /** + * Gets the distribution key. + * + * @return string + */ + public function getDistributionKey() + { + return $this->distributionKey; + } + + /** + * Gets the Doctrine Type name used for the distribution. + * + * @return string + */ + public function getDistributionType() + { + return $this->distributionType; + } + + /** + * Sets Enabled/Disable filtering on the fly. + * + * @param boolean $flag + * + * @return void + */ + public function setFilteringEnabled($flag) + { + $this->filteringEnabled = (bool) $flag; + } + + /** + * {@inheritDoc} + */ + public function selectGlobal() + { + if ($this->conn->isTransactionActive()) { + throw ShardingException::activeTransaction(); + } + + $sql = "USE FEDERATION ROOT WITH RESET"; + $this->conn->exec($sql); + $this->currentDistributionValue = null; + } + + /** + * {@inheritDoc} + */ + public function selectShard($distributionValue) + { + if ($this->conn->isTransactionActive()) { + throw ShardingException::activeTransaction(); + } + + if ($distributionValue === null || is_bool($distributionValue) || !is_scalar($distributionValue)) { + throw ShardingException::noShardDistributionValue(); + } + + $platform = $this->conn->getDatabasePlatform(); + $sql = sprintf( + "USE FEDERATION %s (%s = %s) WITH RESET, FILTERING = %s;", + $platform->quoteIdentifier($this->federationName), + $platform->quoteIdentifier($this->distributionKey), + $this->conn->quote($distributionValue), + ($this->filteringEnabled ? 'ON' : 'OFF') + ); + + $this->conn->exec($sql); + $this->currentDistributionValue = $distributionValue; + } + + /** + * {@inheritDoc} + */ + public function getCurrentDistributionValue() + { + return $this->currentDistributionValue; + } + + /** + * {@inheritDoc} + */ + public function getShards() + { + $sql = "SELECT member_id as id, + distribution_name as distribution_key, + CAST(range_low AS CHAR) AS rangeLow, + CAST(range_high AS CHAR) AS rangeHigh + FROM sys.federation_member_distributions d + INNER JOIN sys.federations f ON f.federation_id = d.federation_id + WHERE f.name = " . $this->conn->quote($this->federationName); + + return $this->conn->fetchAll($sql); + } + + /** + * {@inheritDoc} + */ + public function queryAll($sql, array $params = array(), array $types = array()) + { + $shards = $this->getShards(); + if (!$shards) { + throw new \RuntimeException("No shards found for " . $this->federationName); + } + + $result = array(); + $oldDistribution = $this->getCurrentDistributionValue(); + + foreach ($shards as $shard) { + $this->selectShard($shard['rangeLow']); + foreach ($this->conn->fetchAll($sql, $params, $types) as $row) { + $result[] = $row; + } + } + + if ($oldDistribution === null) { + $this->selectGlobal(); + } else { + $this->selectShard($oldDistribution); + } + + return $result; + } + + /** + * Splits Federation at a given distribution value. + * + * @param mixed $splitDistributionValue + * + * @return void + */ + public function splitFederation($splitDistributionValue) + { + $type = Type::getType($this->distributionType); + + $sql = "ALTER FEDERATION " . $this->getFederationName() . " " . + "SPLIT AT (" . $this->getDistributionKey() . " = " . + $this->conn->quote($splitDistributionValue, $type->getBindingType()) . ")"; + $this->conn->exec($sql); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/Schema/MultiTenantVisitor.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/Schema/MultiTenantVisitor.php new file mode 100644 index 00000000..c1a524f7 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/Schema/MultiTenantVisitor.php @@ -0,0 +1,169 @@ +. + */ + +namespace Doctrine\DBAL\Sharding\SQLAzure\Schema; + +use Doctrine\DBAL\Schema\Visitor\Visitor; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\Index; + +/** + * Converts a single tenant schema into a multi-tenant schema for SQL Azure + * Federations under the following assumptions: + * + * - Every table is part of the multi-tenant application, only explicitly + * excluded tables are non-federated. The behavior of the tables being in + * global or federated database is undefined. It depends on you selecting a + * federation before DDL statements or not. + * - Every Primary key of a federated table is extended by another column + * 'tenant_id' with a default value of the SQLAzure function + * `federation_filtering_value('tenant_id')`. + * - You always have to work with `filtering=On` when using federations with this + * multi-tenant approach. + * - Primary keys are either using globally unique ids (GUID, Table Generator) + * or you explicitly add the tenent_id in every UPDATE or DELETE statement + * (otherwise they will affect the same-id rows from other tenents as well). + * SQLAzure throws errors when you try to create IDENTIY columns on federated + * tables. + * + * @author Benjamin Eberlei + */ +class MultiTenantVisitor implements Visitor +{ + /** + * @var array + */ + private $excludedTables = array(); + + /** + * @var string + */ + private $tenantColumnName; + + /** + * @var string + */ + private $tenantColumnType = 'integer'; + + /** + * Name of the federation distribution, defaulting to the tenantColumnName + * if not specified. + * + * @var string + */ + private $distributionName; + + /** + * @param array $excludedTables + * @param string $tenantColumnName + * @param string|null $distributionName + */ + public function __construct(array $excludedTables = array(), $tenantColumnName = 'tenant_id', $distributionName = null) + { + $this->excludedTables = $excludedTables; + $this->tenantColumnName = $tenantColumnName; + $this->distributionName = $distributionName ?: $tenantColumnName; + } + + /** + * {@inheritdoc} + */ + public function acceptTable(Table $table) + { + if (in_array($table->getName(), $this->excludedTables)) { + return; + } + + $table->addColumn($this->tenantColumnName, $this->tenantColumnType, array( + 'default' => "federation_filtering_value('". $this->distributionName ."')", + )); + + $clusteredIndex = $this->getClusteredIndex($table); + + $indexColumns = $clusteredIndex->getColumns(); + $indexColumns[] = $this->tenantColumnName; + + if ($clusteredIndex->isPrimary()) { + $table->dropPrimaryKey(); + $table->setPrimaryKey($indexColumns); + } else { + $table->dropIndex($clusteredIndex->getName()); + $table->addIndex($indexColumns, $clusteredIndex->getName()); + $table->getIndex($clusteredIndex->getName())->addFlag('clustered'); + } + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * + * @return \Doctrine\DBAL\Schema\Index + * + * @throws \RuntimeException + */ + private function getClusteredIndex($table) + { + foreach ($table->getIndexes() as $index) { + if ($index->isPrimary() && ! $index->hasFlag('nonclustered')) { + return $index; + } elseif ($index->hasFlag('clustered')) { + return $index; + } + } + throw new \RuntimeException("No clustered index found on table " . $table->getName()); + } + + /** + * {@inheritdoc} + */ + public function acceptSchema(Schema $schema) + { + } + + /** + * {@inheritdoc} + */ + public function acceptColumn(Table $table, Column $column) + { + } + + /** + * {@inheritdoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + } + + /** + * {@inheritdoc} + */ + public function acceptIndex(Table $table, Index $index) + { + } + + /** + * {@inheritdoc} + */ + public function acceptSequence(Sequence $sequence) + { + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/MultiTenantShardChoser.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/MultiTenantShardChoser.php new file mode 100644 index 00000000..f1d51b8c --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/MultiTenantShardChoser.php @@ -0,0 +1,39 @@ +. + */ + +namespace Doctrine\DBAL\Sharding\ShardChoser; + +use Doctrine\DBAL\Sharding\PoolingShardConnection; + +/** + * The MultiTenant Shard choser assumes that the distribution value directly + * maps to the shard id. + * + * @author Benjamin Eberlei + */ +class MultiTenantShardChoser implements ShardChoser +{ + /** + * {@inheritdoc} + */ + public function pickShard($distributionValue, PoolingShardConnection $conn) + { + return $distributionValue; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/ShardChoser.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/ShardChoser.php new file mode 100644 index 00000000..a7b85a66 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/ShardChoser.php @@ -0,0 +1,41 @@ +. + */ + +namespace Doctrine\DBAL\Sharding\ShardChoser; + +use Doctrine\DBAL\Sharding\PoolingShardConnection; + +/** + * Given a distribution value this shard-choser strategy will pick the shard to + * connect to for retrieving rows with the distribution value. + * + * @author Benjamin Eberlei + */ +interface ShardChoser +{ + /** + * Picks a shard for the given distribution value. + * + * @param string $distributionValue + * @param \Doctrine\DBAL\Sharding\PoolingShardConnection $conn + * + * @return integer + */ + function pickShard($distributionValue, PoolingShardConnection $conn); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardManager.php new file mode 100644 index 00000000..b742793b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardManager.php @@ -0,0 +1,93 @@ +. + */ + +namespace Doctrine\DBAL\Sharding; + +/** + * Sharding Manager gives access to APIs to implementing sharding on top of + * Doctrine\DBAL\Connection instances. + * + * For simplicity and developer ease-of-use (and understanding) the sharding + * API only covers single shard queries, no fan-out support. It is primarily + * suited for multi-tenant applications. + * + * The assumption about sharding here + * is that a distribution value can be found that gives access to all the + * necessary data for all use-cases. Switching between shards should be done with + * caution, especially if lazy loading is implemented. Any query is always + * executed against the last shard that was selected. If a query is created for + * a shard Y but then a shard X is selected when its actually executed you + * will hit the wrong shard. + * + * @author Benjamin Eberlei + */ +interface ShardManager +{ + /** + * Selects global database with global data. + * + * This is the default database that is connected when no shard is + * selected. + * + * @return void + */ + function selectGlobal(); + + /** + * Selects the shard against which the queries after this statement will be issued. + * + * @param string $distributionValue + * + * @return void + * + * @throws \Doctrine\DBAL\Sharding\ShardingException If no value is passed as shard identifier. + */ + function selectShard($distributionValue); + + /** + * Gets the distribution value currently used for sharding. + * + * @return string + */ + function getCurrentDistributionValue(); + + /** + * Gets information about the amount of shards and other details. + * + * Format is implementation specific, each shard is one element and has an + * 'id' attribute at least. + * + * @return array + */ + function getShards(); + + /** + * Queries all shards in undefined order and return the results appended to + * each other. Restore the previous distribution value after execution. + * + * Using {@link \Doctrine\DBAL\Connection::fetchAll} to retrieve rows internally. + * + * @param string $sql + * @param array $params + * @param array $types + * + * @return array + */ + function queryAll($sql, array $params, array $types); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardingException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardingException.php new file mode 100644 index 00000000..407bf847 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardingException.php @@ -0,0 +1,78 @@ +. + */ + +namespace Doctrine\DBAL\Sharding; + +use Doctrine\DBAL\DBALException; + +/** + * Sharding related Exceptions + * + * @since 2.3 + */ +class ShardingException extends DBALException +{ + /** + * @return \Doctrine\DBAL\Sharding\ShardingException + */ + static public function notImplemented() + { + return new self("This functionality is not implemented with this sharding provider.", 1331557937); + } + + /** + * @return \Doctrine\DBAL\Sharding\ShardingException + */ + static public function missingDefaultFederationName() + { + return new self("SQLAzure requires a federation name to be set during sharding configuration.", 1332141280); + } + + /** + * @return \Doctrine\DBAL\Sharding\ShardingException + */ + static public function missingDefaultDistributionKey() + { + return new self("SQLAzure requires a distribution key to be set during sharding configuration.", 1332141329); + } + + /** + * @return \Doctrine\DBAL\Sharding\ShardingException + */ + static public function activeTransaction() + { + return new self("Cannot switch shard during an active transaction.", 1332141766); + } + + /** + * @return \Doctrine\DBAL\Sharding\ShardingException + */ + static public function noShardDistributionValue() + { + return new self("You have to specify a string or integer as shard distribution value.", 1332142103); + } + + /** + * @return \Doctrine\DBAL\Sharding\ShardingException + */ + static public function missingDistributionType() + { + return new self("You have to specify a sharding distribution type such as 'integer', 'string', 'guid'."); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Statement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Statement.php new file mode 100644 index 00000000..77d0584e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Statement.php @@ -0,0 +1,315 @@ +. + */ + +namespace Doctrine\DBAL; + +use PDO; +use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Driver\Statement as DriverStatement; + +/** + * A thin wrapper around a Doctrine\DBAL\Driver\Statement that adds support + * for logging, DBAL mapping types, etc. + * + * @author Roman Borschel + * @since 2.0 + */ +class Statement implements \IteratorAggregate, DriverStatement +{ + /** + * The SQL statement. + * + * @var string + */ + protected $sql; + + /** + * The bound parameters. + * + * @var array + */ + protected $params = array(); + + /** + * The parameter types. + * + * @var array + */ + protected $types = array(); + + /** + * The underlying driver statement. + * + * @var \Doctrine\DBAL\Driver\Statement + */ + protected $stmt; + + /** + * The underlying database platform. + * + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + protected $platform; + + /** + * The connection this statement is bound to and executed on. + * + * @var \Doctrine\DBAL\Connection + */ + protected $conn; + + /** + * Creates a new Statement for the given SQL and Connection. + * + * @param string $sql The SQL of the statement. + * @param \Doctrine\DBAL\Connection $conn The connection on which the statement should be executed. + */ + public function __construct($sql, Connection $conn) + { + $this->sql = $sql; + $this->stmt = $conn->getWrappedConnection()->prepare($sql); + $this->conn = $conn; + $this->platform = $conn->getDatabasePlatform(); + } + + /** + * Binds a parameter value to the statement. + * + * The value can optionally be bound with a PDO binding type or a DBAL mapping type. + * If bound with a DBAL mapping type, the binding type is derived from the mapping + * type and the value undergoes the conversion routines of the mapping type before + * being bound. + * + * @param string $name The name or position of the parameter. + * @param mixed $value The value of the parameter. + * @param mixed $type Either a PDO binding type or a DBAL mapping type name or instance. + * + * @return boolean TRUE on success, FALSE on failure. + */ + public function bindValue($name, $value, $type = null) + { + $this->params[$name] = $value; + $this->types[$name] = $type; + if ($type !== null) { + if (is_string($type)) { + $type = Type::getType($type); + } + if ($type instanceof Type) { + $value = $type->convertToDatabaseValue($value, $this->platform); + $bindingType = $type->getBindingType(); + } else { + $bindingType = $type; // PDO::PARAM_* constants + } + + return $this->stmt->bindValue($name, $value, $bindingType); + } else { + return $this->stmt->bindValue($name, $value); + } + } + + /** + * Binds a parameter to a value by reference. + * + * Binding a parameter by reference does not support DBAL mapping types. + * + * @param string $name The name or position of the parameter. + * @param mixed $var The reference to the variable to bind. + * @param integer $type The PDO binding type. + * @param integer|null $length Must be specified when using an OUT bind + * so that PHP allocates enough memory to hold the returned value. + * + * @return boolean TRUE on success, FALSE on failure. + */ + public function bindParam($name, &$var, $type = PDO::PARAM_STR, $length = null) + { + $this->params[$name] = $var; + $this->types[$name] = $type; + + return $this->stmt->bindParam($name, $var, $type, $length); + } + + /** + * Executes the statement with the currently bound parameters. + * + * @param array|null $params + * + * @return boolean TRUE on success, FALSE on failure. + * + * @throws \Doctrine\DBAL\DBALException + */ + public function execute($params = null) + { + if (is_array($params)) { + $this->params = $params; + } + + $logger = $this->conn->getConfiguration()->getSQLLogger(); + if ($logger) { + $logger->startQuery($this->sql, $this->params, $this->types); + } + + try { + $stmt = $this->stmt->execute($params); + } catch (\Exception $ex) { + if ($logger) { + $logger->stopQuery(); + } + throw DBALException::driverExceptionDuringQuery( + $this->conn->getDriver(), + $ex, + $this->sql, + $this->conn->resolveParams($this->params, $this->types) + ); + } + + if ($logger) { + $logger->stopQuery(); + } + $this->params = array(); + $this->types = array(); + + return $stmt; + } + + /** + * Closes the cursor, freeing the database resources used by this statement. + * + * @return boolean TRUE on success, FALSE on failure. + */ + public function closeCursor() + { + return $this->stmt->closeCursor(); + } + + /** + * Returns the number of columns in the result set. + * + * @return integer + */ + public function columnCount() + { + return $this->stmt->columnCount(); + } + + /** + * Fetches the SQLSTATE associated with the last operation on the statement. + * + * @return string + */ + public function errorCode() + { + return $this->stmt->errorCode(); + } + + /** + * Fetches extended error information associated with the last operation on the statement. + * + * @return array + */ + public function errorInfo() + { + return $this->stmt->errorInfo(); + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + if ($arg2 === null) { + return $this->stmt->setFetchMode($fetchMode); + } elseif ($arg3 === null) { + return $this->stmt->setFetchMode($fetchMode, $arg2); + } + + return $this->stmt->setFetchMode($fetchMode, $arg2, $arg3); + } + + /** + * Required by interface IteratorAggregate. + * + * {@inheritdoc} + */ + public function getIterator() + { + return $this->stmt; + } + + /** + * Fetches the next row from a result set. + * + * @param integer|null $fetchMode + * + * @return mixed The return value of this function on success depends on the fetch type. + * In all cases, FALSE is returned on failure. + */ + public function fetch($fetchMode = null) + { + return $this->stmt->fetch($fetchMode); + } + + /** + * Returns an array containing all of the result set rows. + * + * @param integer|null $fetchMode + * @param mixed $fetchArgument + * + * @return array An array containing all of the remaining rows in the result set. + */ + public function fetchAll($fetchMode = null, $fetchArgument = 0) + { + if ($fetchArgument !== 0) { + return $this->stmt->fetchAll($fetchMode, $fetchArgument); + } + + return $this->stmt->fetchAll($fetchMode); + } + + /** + * Returns a single column from the next row of a result set. + * + * @param integer $columnIndex + * + * @return mixed A single column from the next row of a result set or FALSE if there are no more rows. + */ + public function fetchColumn($columnIndex = 0) + { + return $this->stmt->fetchColumn($columnIndex); + } + + /** + * Returns the number of rows affected by the last execution of this statement. + * + * @return integer The number of affected rows. + */ + public function rowCount() + { + return $this->stmt->rowCount(); + } + + /** + * Gets the wrapped driver statement. + * + * @return \Doctrine\DBAL\Driver\Statement + */ + public function getWrappedStatement() + { + return $this->stmt; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ImportCommand.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ImportCommand.php new file mode 100644 index 00000000..4d76ca92 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ImportCommand.php @@ -0,0 +1,130 @@ +. + */ + +namespace Doctrine\DBAL\Tools\Console\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Task for executing arbitrary SQL that can come from a file or directly from + * the command line. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class ImportCommand extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('dbal:import') + ->setDescription('Import SQL file(s) directly to Database.') + ->setDefinition(array( + new InputArgument( + 'file', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'File path(s) of SQL to be executed.' + ) + )) + ->setHelp(<<getHelper('db')->getConnection(); + + if (($fileNames = $input->getArgument('file')) !== null) { + foreach ((array) $fileNames as $fileName) { + $filePath = realpath($fileName); + + // Phar compatibility. + if (false === $filePath) { + $filePath = $fileName; + } + + if ( ! file_exists($filePath)) { + throw new \InvalidArgumentException( + sprintf("SQL file '%s' does not exist.", $filePath) + ); + } elseif ( ! is_readable($filePath)) { + throw new \InvalidArgumentException( + sprintf("SQL file '%s' does not have read permissions.", $filePath) + ); + } + + $output->write(sprintf("Processing file '%s'... ", $filePath)); + $sql = file_get_contents($filePath); + + if ($conn instanceof \Doctrine\DBAL\Driver\PDOConnection) { + // PDO Drivers + try { + $lines = 0; + + $stmt = $conn->prepare($sql); + $stmt->execute(); + + do { + // Required due to "MySQL has gone away!" issue + $stmt->fetch(); + $stmt->closeCursor(); + + $lines++; + } while ($stmt->nextRowset()); + + $output->write(sprintf('%d statements executed!', $lines) . PHP_EOL); + } catch (\PDOException $e) { + $output->write('error!' . PHP_EOL); + + throw new \RuntimeException($e->getMessage(), $e->getCode(), $e); + } + } else { + // Non-PDO Drivers (ie. OCI8 driver) + $stmt = $conn->prepare($sql); + $rs = $stmt->execute(); + + if ($rs) { + $output->writeln('OK!' . PHP_EOL); + } else { + $error = $stmt->errorInfo(); + + $output->write('error!' . PHP_EOL); + + throw new \RuntimeException($error[2], $error[0]); + } + + $stmt->closeCursor(); + } + } + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php new file mode 100644 index 00000000..f7acd0a8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php @@ -0,0 +1,172 @@ +. + */ + +namespace Doctrine\DBAL\Tools\Console\Command; + +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Doctrine\DBAL\Platforms\Keywords\ReservedKeywordsValidator; + +class ReservedWordsCommand extends Command +{ + /** + * @var array + */ + private $keywordListClasses = array( + 'mysql' => 'Doctrine\DBAL\Platforms\Keywords\MySQLKeywords', + 'mysql57' => 'Doctrine\DBAL\Platforms\Keywords\MySQL57Keywords', + 'sqlserver' => 'Doctrine\DBAL\Platforms\Keywords\SQLServerKeywords', + 'sqlserver2005' => 'Doctrine\DBAL\Platforms\Keywords\SQLServer2005Keywords', + 'sqlserver2008' => 'Doctrine\DBAL\Platforms\Keywords\SQLServer2008Keywords', + 'sqlserver2012' => 'Doctrine\DBAL\Platforms\Keywords\SQLServer2012Keywords', + 'sqlite' => 'Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords', + 'pgsql' => 'Doctrine\DBAL\Platforms\Keywords\PostgreSQLKeywords', + 'pgsql91' => 'Doctrine\DBAL\Platforms\Keywords\PostgreSQL91Keywords', + 'pgsql92' => 'Doctrine\DBAL\Platforms\Keywords\PostgreSQL92Keywords', + 'oracle' => 'Doctrine\DBAL\Platforms\Keywords\OracleKeywords', + 'db2' => 'Doctrine\DBAL\Platforms\Keywords\DB2Keywords', + 'sqlanywhere' => 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhereKeywords', + 'sqlanywhere11' => 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhere11Keywords', + 'sqlanywhere12' => 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhere12Keywords', + 'sqlanywhere16' => 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhere16Keywords', + ); + + /** + * If you want to add or replace a keywords list use this command. + * + * @param string $name + * @param string $class + * + * @return void + */ + public function setKeywordListClass($name, $class) + { + $this->keywordListClasses[$name] = $class; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('dbal:reserved-words') + ->setDescription('Checks if the current database contains identifiers that are reserved.') + ->setDefinition(array( + new InputOption( + 'list', 'l', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Keyword-List name.' + ) + )) + ->setHelp(<<%command.full_name% + +If you want to check against specific dialects you can +pass them to the command: + + %command.full_name% mysql pgsql + +The following keyword lists are currently shipped with Doctrine: + + * mysql + * mysql57 + * pgsql + * pgsql92 + * sqlite + * oracle + * sqlserver + * sqlserver2005 + * sqlserver2008 + * sqlserver2012 + * sqlanywhere + * sqlanywhere11 + * sqlanywhere12 + * sqlanywhere16 + * db2 (Not checked by default) +EOT + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + /* @var $conn \Doctrine\DBAL\Connection */ + $conn = $this->getHelper('db')->getConnection(); + + $keywordLists = (array) $input->getOption('list'); + if ( ! $keywordLists) { + $keywordLists = array( + 'mysql', + 'mysql57', + 'pgsql', + 'pgsql92', + 'sqlite', + 'oracle', + 'sqlserver', + 'sqlserver2005', + 'sqlserver2008', + 'sqlserver2012', + 'sqlanywhere', + 'sqlanywhere11', + 'sqlanywhere12', + 'sqlanywhere16', + ); + } + + $keywords = array(); + foreach ($keywordLists as $keywordList) { + if (!isset($this->keywordListClasses[$keywordList])) { + throw new \InvalidArgumentException( + "There exists no keyword list with name '" . $keywordList . "'. ". + "Known lists: " . implode(", ", array_keys($this->keywordListClasses)) + ); + } + $class = $this->keywordListClasses[$keywordList]; + $keywords[] = new $class; + } + + $output->write('Checking keyword violations for ' . implode(", ", $keywordLists) . "...", true); + + /* @var $schema \Doctrine\DBAL\Schema\Schema */ + $schema = $conn->getSchemaManager()->createSchema(); + $visitor = new ReservedKeywordsValidator($keywords); + $schema->visit($visitor); + + $violations = $visitor->getViolations(); + if (count($violations) == 0) { + $output->write("No reserved keywords violations have been found!", true); + } else { + $output->write('There are ' . count($violations) . ' reserved keyword violations in your database schema:', true); + foreach ($violations as $violation) { + $output->write(' - ' . $violation, true); + } + + return 1; + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php new file mode 100644 index 00000000..b2b2d970 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php @@ -0,0 +1,88 @@ +. + */ + +namespace Doctrine\DBAL\Tools\Console\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputOption; + +/** + * Task for executing arbitrary SQL that can come from a file or directly from + * the command line. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class RunSqlCommand extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('dbal:run-sql') + ->setDescription('Executes arbitrary SQL directly from the command line.') + ->setDefinition(array( + new InputArgument('sql', InputArgument::REQUIRED, 'The SQL statement to execute.'), + new InputOption('depth', null, InputOption::VALUE_REQUIRED, 'Dumping depth of result set.', 7) + )) + ->setHelp(<<getHelper('db')->getConnection(); + + if (($sql = $input->getArgument('sql')) === null) { + throw new \RuntimeException("Argument 'SQL' is required in order to execute this command correctly."); + } + + $depth = $input->getOption('depth'); + + if ( ! is_numeric($depth)) { + throw new \LogicException("Option 'depth' must contains an integer value"); + } + + if (stripos($sql, 'select') === 0) { + $resultSet = $conn->fetchAll($sql); + } else { + $resultSet = $conn->executeUpdate($sql); + } + + ob_start(); + \Doctrine\Common\Util\Debug::dump($resultSet, (int) $depth); + $message = ob_get_clean(); + + $output->write($message); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/ConsoleRunner.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/ConsoleRunner.php new file mode 100644 index 00000000..3fb9c050 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/ConsoleRunner.php @@ -0,0 +1,107 @@ +. + */ + +namespace Doctrine\DBAL\Tools\Console; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Tools\Console\Command\ImportCommand; +use Doctrine\DBAL\Tools\Console\Command\ReservedWordsCommand; +use Doctrine\DBAL\Tools\Console\Command\RunSqlCommand; +use Symfony\Component\Console\Helper\HelperSet; +use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper; +use Symfony\Component\Console\Application; +use Doctrine\DBAL\Version; + +/** + * Handles running the Console Tools inside Symfony Console context. + */ +class ConsoleRunner +{ + /** + * Create a Symfony Console HelperSet + * + * @param Connection $connection + * + * @return HelperSet + */ + static public function createHelperSet(Connection $connection) + { + return new HelperSet(array( + 'db' => new ConnectionHelper($connection) + )); + } + + /** + * Runs console with the given helperset. + * + * @param \Symfony\Component\Console\Helper\HelperSet $helperSet + * @param \Symfony\Component\Console\Command\Command[] $commands + * + * @return void + */ + static public function run(HelperSet $helperSet, $commands = array()) + { + $cli = new Application('Doctrine Command Line Interface', Version::VERSION); + + $cli->setCatchExceptions(true); + $cli->setHelperSet($helperSet); + + self::addCommands($cli); + + $cli->addCommands($commands); + $cli->run(); + } + + /** + * @param Application $cli + * + * @return void + */ + static public function addCommands(Application $cli) + { + $cli->addCommands(array( + new RunSqlCommand(), + new ImportCommand(), + new ReservedWordsCommand(), + )); + } + + /** + * Prints the instructions to create a configuration file + */ + static public function printCliConfigTemplate() + { + echo <<<'HELP' +You are missing a "cli-config.php" or "config/cli-config.php" file in your +project, which is required to get the Doctrine-DBAL Console working. You can use the +following sample as a template: + +. + */ + +namespace Doctrine\DBAL\Tools\Console\Helper; + +use Symfony\Component\Console\Helper\Helper; +use Doctrine\DBAL\Connection; + +/** + * Doctrine CLI Connection Helper. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class ConnectionHelper extends Helper +{ + /** + * The Doctrine database Connection. + * + * @var \Doctrine\DBAL\Connection + */ + protected $_connection; + + /** + * Constructor. + * + * @param \Doctrine\DBAL\Connection $connection The Doctrine database Connection. + */ + public function __construct(Connection $connection) + { + $this->_connection = $connection; + } + + /** + * Retrieves the Doctrine database Connection. + * + * @return \Doctrine\DBAL\Connection + */ + public function getConnection() + { + return $this->_connection; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'connection'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ArrayType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ArrayType.php new file mode 100644 index 00000000..f8c62cb7 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ArrayType.php @@ -0,0 +1,81 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps a PHP array to a clob SQL type. + * + * @since 2.0 + */ +class ArrayType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getClobTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + // @todo 3.0 - $value === null check to save real NULL in database + return serialize($value); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return null; + } + + $value = (is_resource($value)) ? stream_get_contents($value) : $value; + $val = unserialize($value); + if ($val === false && $value != 'b:0;') { + throw ConversionException::conversionFailed($value, $this->getName()); + } + + return $val; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::TARRAY; + } + + /** + * {@inheritdoc} + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return true; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BigIntType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BigIntType.php new file mode 100644 index 00000000..1a9a6c37 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BigIntType.php @@ -0,0 +1,63 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps a database BIGINT to a PHP string. + * + * @author robo + * @since 2.0 + */ +class BigIntType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::BIGINT; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getBigIntTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function getBindingType() + { + return \PDO::PARAM_STR; + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return (null === $value) ? null : (string) $value; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BinaryType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BinaryType.php new file mode 100644 index 00000000..a22a4405 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BinaryType.php @@ -0,0 +1,78 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps ab SQL BINARY/VARBINARY to a PHP resource stream. + * + * @author Steve Müller + * @since 2.5 + */ +class BinaryType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getBinaryTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if (null === $value) { + return null; + } + + if (is_string($value)) { + $fp = fopen('php://temp', 'rb+'); + fwrite($fp, $value); + fseek($fp, 0); + $value = $fp; + } + + if ( ! is_resource($value)) { + throw ConversionException::conversionFailed($value, self::BINARY); + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::BINARY; + } + + /** + * {@inheritdoc} + */ + public function getBindingType() + { + return \PDO::PARAM_LOB; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BlobType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BlobType.php new file mode 100644 index 00000000..bd89301e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BlobType.php @@ -0,0 +1,77 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL BLOB to a PHP resource stream. + * + * @since 2.2 + */ +class BlobType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getBlobTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if (null === $value) { + return null; + } + + if (is_string($value)) { + $fp = fopen('php://temp', 'rb+'); + fwrite($fp, $value); + fseek($fp, 0); + $value = $fp; + } + + if ( ! is_resource($value)) { + throw ConversionException::conversionFailed($value, self::BLOB); + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::BLOB; + } + + /** + * {@inheritdoc} + */ + public function getBindingType() + { + return \PDO::PARAM_LOB; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BooleanType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BooleanType.php new file mode 100644 index 00000000..9fea80f1 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BooleanType.php @@ -0,0 +1,70 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL boolean to a PHP boolean. + * + * @since 2.0 + */ +class BooleanType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getBooleanTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return $platform->convertBooleansToDatabaseValue($value); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return $platform->convertFromBoolean($value); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::BOOLEAN; + } + + /** + * {@inheritdoc} + */ + public function getBindingType() + { + return \PDO::PARAM_BOOL; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ConversionException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ConversionException.php new file mode 100644 index 00000000..4675101b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ConversionException.php @@ -0,0 +1,68 @@ +. + */ + +/** + * Conversion Exception is thrown when the database to PHP conversion fails. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +namespace Doctrine\DBAL\Types; + +class ConversionException extends \Doctrine\DBAL\DBALException +{ + /** + * Thrown when a Database to Doctrine Type Conversion fails. + * + * @param string $value + * @param string $toType + * + * @return \Doctrine\DBAL\Types\ConversionException + */ + static public function conversionFailed($value, $toType) + { + $value = (strlen($value) > 32) ? substr($value, 0, 20) . "..." : $value; + + return new self('Could not convert database value "' . $value . '" to Doctrine Type ' . $toType); + } + + /** + * Thrown when a Database to Doctrine Type Conversion fails and we can make a statement + * about the expected format. + * + * @param string $value + * @param string $toType + * @param string $expectedFormat + * + * @return \Doctrine\DBAL\Types\ConversionException + */ + static public function conversionFailedFormat($value, $toType, $expectedFormat) + { + $value = (strlen($value) > 32) ? substr($value, 0, 20) . "..." : $value; + + return new self( + 'Could not convert database value "' . $value . '" to Doctrine Type ' . + $toType . '. Expected format: ' . $expectedFormat + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeType.php new file mode 100644 index 00000000..c5715a79 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeType.php @@ -0,0 +1,77 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime object. + * + * @since 2.0 + */ +class DateTimeType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::DATETIME; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getDateTimeTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return ($value !== null) + ? $value->format($platform->getDateTimeFormatString()) : null; + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof \DateTime) { + return $value; + } + + $val = \DateTime::createFromFormat($platform->getDateTimeFormatString(), $value); + + if ( ! $val) { + $val = date_create($value); + } + + if ( ! $val) { + throw ConversionException::conversionFailedFormat($value, $this->getName(), $platform->getDateTimeFormatString()); + } + + return $val; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeTzType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeTzType.php new file mode 100644 index 00000000..2a9c6fa7 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeTzType.php @@ -0,0 +1,90 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * DateTime type saving additional timezone information. + * + * Caution: Databases are not necessarily experts at storing timezone related + * data of dates. First, of all the supported vendors only PostgreSQL and Oracle + * support storing Timezone data. But those two don't save the actual timezone + * attached to a DateTime instance (for example "Europe/Berlin" or "America/Montreal") + * but the current offset of them related to UTC. That means depending on daylight saving times + * or not you may get different offsets. + * + * This datatype makes only sense to use, if your application works with an offset, not + * with an actual timezone that uses transitions. Otherwise your DateTime instance + * attached with a timezone such as Europe/Berlin gets saved into the database with + * the offset and re-created from persistence with only the offset, not the original timezone + * attached. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class DateTimeTzType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::DATETIMETZ; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getDateTimeTzTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return ($value !== null) + ? $value->format($platform->getDateTimeTzFormatString()) : null; + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof \DateTime) { + return $value; + } + + $val = \DateTime::createFromFormat($platform->getDateTimeTzFormatString(), $value); + if ( ! $val) { + throw ConversionException::conversionFailedFormat($value, $this->getName(), $platform->getDateTimeTzFormatString()); + } + + return $val; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateType.php new file mode 100644 index 00000000..9d337558 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateType.php @@ -0,0 +1,72 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL DATE to a PHP Date object. + * + * @since 2.0 + */ +class DateType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::DATE; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getDateTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return ($value !== null) + ? $value->format($platform->getDateFormatString()) : null; + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof \DateTime) { + return $value; + } + + $val = \DateTime::createFromFormat('!'.$platform->getDateFormatString(), $value); + if ( ! $val) { + throw ConversionException::conversionFailedFormat($value, $this->getName(), $platform->getDateFormatString()); + } + + return $val; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DecimalType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DecimalType.php new file mode 100644 index 00000000..df251d71 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DecimalType.php @@ -0,0 +1,54 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL DECIMAL to a PHP string. + * + * @since 2.0 + */ +class DecimalType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::DECIMAL; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getDecimalTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return (null === $value) ? null : $value; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/FloatType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/FloatType.php new file mode 100644 index 00000000..87f9c32a --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/FloatType.php @@ -0,0 +1,49 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +class FloatType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::FLOAT; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getFloatDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return (null === $value) ? null : (double) $value; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/GuidType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/GuidType.php new file mode 100644 index 00000000..761d58a8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/GuidType.php @@ -0,0 +1,55 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Represents a GUID/UUID datatype (both are actually synonyms) in the database. + * + * @author Benjamin Eberlei + * @since 2.3 + */ +class GuidType extends StringType +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getGuidTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::GUID; + } + + /** + * {@inheritdoc} + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return !$platform->hasNativeGuidType(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/IntegerType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/IntegerType.php new file mode 100644 index 00000000..6fe6375e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/IntegerType.php @@ -0,0 +1,63 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL INT to a PHP integer. + * + * @author Roman Borschel + * @since 2.0 + */ +class IntegerType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::INTEGER; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getIntegerTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return (null === $value) ? null : (int) $value; + } + + /** + * {@inheritdoc} + */ + public function getBindingType() + { + return \PDO::PARAM_INT; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/JsonArrayType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/JsonArrayType.php new file mode 100644 index 00000000..79c32594 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/JsonArrayType.php @@ -0,0 +1,81 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Array Type which can be used to generate json arrays. + * + * @since 2.3 + * @author Johannes M. Schmitt + */ +class JsonArrayType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getJsonTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if (null === $value) { + return null; + } + + return json_encode($value); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value === '') { + return array(); + } + + $value = (is_resource($value)) ? stream_get_contents($value) : $value; + + return json_decode($value, true); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::JSON_ARRAY; + } + + /** + * {@inheritdoc} + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return ! $platform->hasNativeJsonType(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ObjectType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ObjectType.php new file mode 100644 index 00000000..0b1f87a2 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ObjectType.php @@ -0,0 +1,80 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps a PHP object to a clob SQL type. + * + * @since 2.0 + */ +class ObjectType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getClobTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return serialize($value); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return null; + } + + $value = (is_resource($value)) ? stream_get_contents($value) : $value; + $val = unserialize($value); + if ($val === false && $value !== 'b:0;') { + throw ConversionException::conversionFailed($value, $this->getName()); + } + + return $val; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::OBJECT; + } + + /** + * {@inheritdoc} + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return true; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/SimpleArrayType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/SimpleArrayType.php new file mode 100644 index 00000000..47c821be --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/SimpleArrayType.php @@ -0,0 +1,83 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Array Type which can be used for simple values. + * + * Only use this type if you are sure that your values cannot contain a ",". + * + * @since 2.3 + * @author Johannes M. Schmitt + */ +class SimpleArrayType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getClobTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if (!$value) { + return null; + } + + return implode(',', $value); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return array(); + } + + $value = (is_resource($value)) ? stream_get_contents($value) : $value; + + return explode(',', $value); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::SIMPLE_ARRAY; + } + + /** + * {@inheritdoc} + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return true; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/SmallIntType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/SmallIntType.php new file mode 100644 index 00000000..7ebae6eb --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/SmallIntType.php @@ -0,0 +1,62 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps a database SMALLINT to a PHP integer. + * + * @author robo + */ +class SmallIntType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::SMALLINT; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getSmallIntTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return (null === $value) ? null : (int) $value; + } + + /** + * {@inheritdoc} + */ + public function getBindingType() + { + return \PDO::PARAM_INT; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/StringType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/StringType.php new file mode 100644 index 00000000..39bd32db --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/StringType.php @@ -0,0 +1,54 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL VARCHAR to a PHP string. + * + * @since 2.0 + */ +class StringType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getVarcharTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function getDefaultLength(AbstractPlatform $platform) + { + return $platform->getVarcharDefaultLength(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::STRING; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TextType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TextType.php new file mode 100644 index 00000000..4055ac88 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TextType.php @@ -0,0 +1,54 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL CLOB to a PHP string. + * + * @since 2.0 + */ +class TextType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getClobTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return (is_resource($value)) ? stream_get_contents($value) : $value; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::TEXT; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TimeType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TimeType.php new file mode 100644 index 00000000..554d56dc --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TimeType.php @@ -0,0 +1,72 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL TIME to a PHP DateTime object. + * + * @since 2.0 + */ +class TimeType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::TIME; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getTimeTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return ($value !== null) + ? $value->format($platform->getTimeFormatString()) : null; + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof \DateTime) { + return $value; + } + + $val = \DateTime::createFromFormat('!' . $platform->getTimeFormatString(), $value); + if ( ! $val) { + throw ConversionException::conversionFailedFormat($value, $this->getName(), $platform->getTimeFormatString()); + } + + return $val; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/Type.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/Type.php new file mode 100644 index 00000000..9fa464f5 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/Type.php @@ -0,0 +1,341 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\DBALException; + +/** + * The base class for so-called Doctrine mapping types. + * + * A Type object is obtained by calling the static {@link getType()} method. + * + * @author Roman Borschel + * @author Benjamin Eberlei + * @since 2.0 + */ +abstract class Type +{ + const TARRAY = 'array'; + const SIMPLE_ARRAY = 'simple_array'; + const JSON_ARRAY = 'json_array'; + const BIGINT = 'bigint'; + const BOOLEAN = 'boolean'; + const DATETIME = 'datetime'; + const DATETIMETZ = 'datetimetz'; + const DATE = 'date'; + const TIME = 'time'; + const DECIMAL = 'decimal'; + const INTEGER = 'integer'; + const OBJECT = 'object'; + const SMALLINT = 'smallint'; + const STRING = 'string'; + const TEXT = 'text'; + const BINARY = 'binary'; + const BLOB = 'blob'; + const FLOAT = 'float'; + const GUID = 'guid'; + + /** + * Map of already instantiated type objects. One instance per type (flyweight). + * + * @var array + */ + private static $_typeObjects = array(); + + /** + * The map of supported doctrine mapping types. + * + * @var array + */ + private static $_typesMap = array( + self::TARRAY => 'Doctrine\DBAL\Types\ArrayType', + self::SIMPLE_ARRAY => 'Doctrine\DBAL\Types\SimpleArrayType', + self::JSON_ARRAY => 'Doctrine\DBAL\Types\JsonArrayType', + self::OBJECT => 'Doctrine\DBAL\Types\ObjectType', + self::BOOLEAN => 'Doctrine\DBAL\Types\BooleanType', + self::INTEGER => 'Doctrine\DBAL\Types\IntegerType', + self::SMALLINT => 'Doctrine\DBAL\Types\SmallIntType', + self::BIGINT => 'Doctrine\DBAL\Types\BigIntType', + self::STRING => 'Doctrine\DBAL\Types\StringType', + self::TEXT => 'Doctrine\DBAL\Types\TextType', + self::DATETIME => 'Doctrine\DBAL\Types\DateTimeType', + self::DATETIMETZ => 'Doctrine\DBAL\Types\DateTimeTzType', + self::DATE => 'Doctrine\DBAL\Types\DateType', + self::TIME => 'Doctrine\DBAL\Types\TimeType', + self::DECIMAL => 'Doctrine\DBAL\Types\DecimalType', + self::FLOAT => 'Doctrine\DBAL\Types\FloatType', + self::BINARY => 'Doctrine\DBAL\Types\BinaryType', + self::BLOB => 'Doctrine\DBAL\Types\BlobType', + self::GUID => 'Doctrine\DBAL\Types\GuidType', + ); + + /** + * Prevents instantiation and forces use of the factory method. + */ + final private function __construct() + { + } + + /** + * Converts a value from its PHP representation to its database representation + * of this type. + * + * @param mixed $value The value to convert. + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The currently used database platform. + * + * @return mixed The database representation of the value. + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return $value; + } + + /** + * Converts a value from its database representation to its PHP representation + * of this type. + * + * @param mixed $value The value to convert. + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The currently used database platform. + * + * @return mixed The PHP representation of the value. + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return $value; + } + + /** + * Gets the default length of this type. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return integer|null + * + * @todo Needed? + */ + public function getDefaultLength(AbstractPlatform $platform) + { + return null; + } + + /** + * Gets the SQL declaration snippet for a field of this type. + * + * @param array $fieldDeclaration The field declaration. + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The currently used database platform. + * + * @return string + */ + abstract public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform); + + /** + * Gets the name of this type. + * + * @return string + * + * @todo Needed? + */ + abstract public function getName(); + + /** + * Factory method to create type instances. + * Type instances are implemented as flyweights. + * + * @param string $name The name of the type (as returned by getName()). + * + * @return \Doctrine\DBAL\Types\Type + * + * @throws \Doctrine\DBAL\DBALException + */ + public static function getType($name) + { + if ( ! isset(self::$_typeObjects[$name])) { + if ( ! isset(self::$_typesMap[$name])) { + throw DBALException::unknownColumnType($name); + } + self::$_typeObjects[$name] = new self::$_typesMap[$name](); + } + + return self::$_typeObjects[$name]; + } + + /** + * Adds a custom type to the type map. + * + * @param string $name The name of the type. This should correspond to what getName() returns. + * @param string $className The class name of the custom type. + * + * @return void + * + * @throws \Doctrine\DBAL\DBALException + */ + public static function addType($name, $className) + { + if (isset(self::$_typesMap[$name])) { + throw DBALException::typeExists($name); + } + + self::$_typesMap[$name] = $className; + } + + /** + * Checks if exists support for a type. + * + * @param string $name The name of the type. + * + * @return boolean TRUE if type is supported; FALSE otherwise. + */ + public static function hasType($name) + { + return isset(self::$_typesMap[$name]); + } + + /** + * Overrides an already defined type to use a different implementation. + * + * @param string $name + * @param string $className + * + * @return void + * + * @throws \Doctrine\DBAL\DBALException + */ + public static function overrideType($name, $className) + { + if ( ! isset(self::$_typesMap[$name])) { + throw DBALException::typeNotFound($name); + } + + if (isset(self::$_typeObjects[$name])) { + unset(self::$_typeObjects[$name]); + } + + self::$_typesMap[$name] = $className; + } + + /** + * Gets the (preferred) binding type for values of this type that + * can be used when binding parameters to prepared statements. + * + * This method should return one of the PDO::PARAM_* constants, that is, one of: + * + * PDO::PARAM_BOOL + * PDO::PARAM_NULL + * PDO::PARAM_INT + * PDO::PARAM_STR + * PDO::PARAM_LOB + * + * @return integer + */ + public function getBindingType() + { + return \PDO::PARAM_STR; + } + + /** + * Gets the types array map which holds all registered types and the corresponding + * type class + * + * @return array + */ + public static function getTypesMap() + { + return self::$_typesMap; + } + + /** + * @return string + */ + public function __toString() + { + $e = explode('\\', get_class($this)); + + return str_replace('Type', '', end($e)); + } + + /** + * Does working with this column require SQL conversion functions? + * + * This is a metadata function that is required for example in the ORM. + * Usage of {@link convertToDatabaseValueSQL} and + * {@link convertToPHPValueSQL} works for any type and mostly + * does nothing. This method can additionally be used for optimization purposes. + * + * @return boolean + */ + public function canRequireSQLConversion() + { + return false; + } + + /** + * Modifies the SQL expression (identifier, parameter) to convert to a database value. + * + * @param string $sqlExpr + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return string + */ + public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform) + { + return $sqlExpr; + } + + /** + * Modifies the SQL expression (identifier, parameter) to convert to a PHP value. + * + * @param string $sqlExpr + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return string + */ + public function convertToPHPValueSQL($sqlExpr, $platform) + { + return $sqlExpr; + } + + /** + * Gets an array of database types that map to this Doctrine type. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return array + */ + public function getMappedDatabaseTypes(AbstractPlatform $platform) + { + return array(); + } + + /** + * If this Doctrine Type maps to an already mapped database type, + * reverse schema engineering can't take them apart. You need to mark + * one of those types as commented, which will have Doctrine use an SQL + * comment to typehint the actual Doctrine Type. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return boolean + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return false; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/VarDateTimeType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/VarDateTimeType.php new file mode 100644 index 00000000..5cf35f79 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/VarDateTimeType.php @@ -0,0 +1,56 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Variable DateTime Type using date_create() instead of DateTime::createFromFormat(). + * + * This type has performance implications as it runs twice as long as the regular + * {@see DateTimeType}, however in certain PostgreSQL configurations with + * TIMESTAMP(n) columns where n > 0 it is necessary to use this type. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class VarDateTimeType extends DateTimeType +{ + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof \DateTime) { + return $value; + } + + $val = date_create($value); + if ( ! $val) { + throw ConversionException::conversionFailed($value, $this->getName()); + } + + return $val; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Version.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Version.php new file mode 100644 index 00000000..f9dfb72b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Version.php @@ -0,0 +1,53 @@ +. + */ + +namespace Doctrine\DBAL; + +/** + * Class to store and retrieve the version of Doctrine. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class Version +{ + /** + * Current Doctrine Version. + */ + const VERSION = '2.5.12'; + + /** + * Compares a Doctrine version with the current one. + * + * @param string $version The Doctrine version to compare to. + * + * @return integer -1 if older, 0 if it is the same, 1 if version passed as argument is newer. + */ + public static function compare($version) + { + $currentVersion = str_replace(' ', '', strtolower(self::VERSION)); + $version = str_replace(' ', '', $version); + + return version_compare($version, $currentVersion); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/VersionAwarePlatformDriver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/VersionAwarePlatformDriver.php new file mode 100644 index 00000000..53e2d3ab --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/VersionAwarePlatformDriver.php @@ -0,0 +1,47 @@ +. + */ + +namespace Doctrine\DBAL; + +/** + * Contract for a driver that is able to create platform instances by version. + * + * Doctrine uses different platform classes for different vendor versions to + * support the correct features and SQL syntax of each version. + * This interface should be implemented by drivers that are capable to do this + * distinction. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +interface VersionAwarePlatformDriver +{ + /** + * Factory method for creating the appropriate platform instance for the given version. + * + * @param string $version The platform/server version string to evaluate. This should be given in the notation + * the underlying database vendor uses. + * + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + * + * @throws DBALException if the given version string could not be evaluated. + */ + public function createDatabasePlatformForVersion($version); +} diff --git a/vendor/doctrine/inflector/.gitignore b/vendor/doctrine/inflector/.gitignore new file mode 100644 index 00000000..f2cb7f83 --- /dev/null +++ b/vendor/doctrine/inflector/.gitignore @@ -0,0 +1,4 @@ +vendor/ +composer.lock +composer.phar +phpunit.xml diff --git a/vendor/doctrine/inflector/.travis.yml b/vendor/doctrine/inflector/.travis.yml new file mode 100644 index 00000000..9ec68f76 --- /dev/null +++ b/vendor/doctrine/inflector/.travis.yml @@ -0,0 +1,21 @@ +language: php + +sudo: false + +cache: + directory: + - $HOME/.composer/cache + +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + +install: + - composer install -n + +script: + - phpunit diff --git a/vendor/doctrine/inflector/LICENSE b/vendor/doctrine/inflector/LICENSE new file mode 100644 index 00000000..8c38cc1b --- /dev/null +++ b/vendor/doctrine/inflector/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2015 Doctrine Project + +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/vendor/doctrine/inflector/README.md b/vendor/doctrine/inflector/README.md new file mode 100644 index 00000000..acb55a01 --- /dev/null +++ b/vendor/doctrine/inflector/README.md @@ -0,0 +1,6 @@ +# Doctrine Inflector + +Doctrine Inflector is a small library that can perform string manipulations +with regard to upper-/lowercase and singular/plural forms of words. + +[![Build Status](https://travis-ci.org/doctrine/inflector.svg?branch=master)](https://travis-ci.org/doctrine/inflector) diff --git a/vendor/doctrine/inflector/composer.json b/vendor/doctrine/inflector/composer.json new file mode 100644 index 00000000..7e5b2efb --- /dev/null +++ b/vendor/doctrine/inflector/composer.json @@ -0,0 +1,29 @@ +{ + "name": "doctrine/inflector", + "type": "library", + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "keywords": ["string", "inflection", "singularize", "pluralize"], + "homepage": "http://www.doctrine-project.org", + "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"} + ], + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "autoload": { + "psr-0": { "Doctrine\\Common\\Inflector\\": "lib/" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php b/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php new file mode 100644 index 00000000..a53828ab --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php @@ -0,0 +1,482 @@ +. + */ + +namespace Doctrine\Common\Inflector; + +/** + * Doctrine inflector has static methods for inflecting text. + * + * The methods in these classes are from several different sources collected + * across several different php projects and several different authors. The + * original author names and emails are not known. + * + * Pluralize & Singularize implementation are borrowed from CakePHP with some modifications. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Konsta Vesterinen + * @author Jonathan H. Wage + */ +class Inflector +{ + /** + * Plural inflector rules. + * + * @var array + */ + private static $plural = array( + 'rules' => array( + '/(s)tatus$/i' => '\1\2tatuses', + '/(quiz)$/i' => '\1zes', + '/^(ox)$/i' => '\1\2en', + '/([m|l])ouse$/i' => '\1ice', + '/(matr|vert|ind)(ix|ex)$/i' => '\1ices', + '/(x|ch|ss|sh)$/i' => '\1es', + '/([^aeiouy]|qu)y$/i' => '\1ies', + '/(hive)$/i' => '\1s', + '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', + '/sis$/i' => 'ses', + '/([ti])um$/i' => '\1a', + '/(p)erson$/i' => '\1eople', + '/(m)an$/i' => '\1en', + '/(c)hild$/i' => '\1hildren', + '/(f)oot$/i' => '\1eet', + '/(buffal|her|potat|tomat|volcan)o$/i' => '\1\2oes', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', + '/us$/i' => 'uses', + '/(alias)$/i' => '\1es', + '/(analys|ax|cris|test|thes)is$/i' => '\1es', + '/s$/' => 's', + '/^$/' => '', + '/$/' => 's', + ), + 'uninflected' => array( + '.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', 'people', 'cookie' + ), + 'irregular' => array( + 'atlas' => 'atlases', + 'axe' => 'axes', + 'beef' => 'beefs', + 'brother' => 'brothers', + 'cafe' => 'cafes', + 'chateau' => 'chateaux', + 'child' => 'children', + 'cookie' => 'cookies', + 'corpus' => 'corpuses', + 'cow' => 'cows', + 'criterion' => 'criteria', + 'curriculum' => 'curricula', + 'demo' => 'demos', + 'domino' => 'dominoes', + 'echo' => 'echoes', + 'foot' => 'feet', + 'fungus' => 'fungi', + 'ganglion' => 'ganglions', + 'genie' => 'genies', + 'genus' => 'genera', + 'graffito' => 'graffiti', + 'hippopotamus' => 'hippopotami', + 'hoof' => 'hoofs', + 'human' => 'humans', + 'iris' => 'irises', + 'leaf' => 'leaves', + 'loaf' => 'loaves', + 'man' => 'men', + 'medium' => 'media', + 'memorandum' => 'memoranda', + 'money' => 'monies', + 'mongoose' => 'mongooses', + 'motto' => 'mottoes', + 'move' => 'moves', + 'mythos' => 'mythoi', + 'niche' => 'niches', + 'nucleus' => 'nuclei', + 'numen' => 'numina', + 'occiput' => 'occiputs', + 'octopus' => 'octopuses', + 'opus' => 'opuses', + 'ox' => 'oxen', + 'penis' => 'penises', + 'person' => 'people', + 'plateau' => 'plateaux', + 'runner-up' => 'runners-up', + 'sex' => 'sexes', + 'soliloquy' => 'soliloquies', + 'son-in-law' => 'sons-in-law', + 'syllabus' => 'syllabi', + 'testis' => 'testes', + 'thief' => 'thieves', + 'tooth' => 'teeth', + 'tornado' => 'tornadoes', + 'trilby' => 'trilbys', + 'turf' => 'turfs', + 'volcano' => 'volcanoes', + ) + ); + + /** + * Singular inflector rules. + * + * @var array + */ + private static $singular = array( + 'rules' => array( + '/(s)tatuses$/i' => '\1\2tatus', + '/^(.*)(menu)s$/i' => '\1\2', + '/(quiz)zes$/i' => '\\1', + '/(matr)ices$/i' => '\1ix', + '/(vert|ind)ices$/i' => '\1ex', + '/^(ox)en/i' => '\1', + '/(alias)(es)*$/i' => '\1', + '/(buffal|her|potat|tomat|volcan)oes$/i' => '\1o', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', + '/([ftw]ax)es/i' => '\1', + '/(analys|ax|cris|test|thes)es$/i' => '\1is', + '/(shoe|slave)s$/i' => '\1', + '/(o)es$/i' => '\1', + '/ouses$/' => 'ouse', + '/([^a])uses$/' => '\1us', + '/([m|l])ice$/i' => '\1ouse', + '/(x|ch|ss|sh)es$/i' => '\1', + '/(m)ovies$/i' => '\1\2ovie', + '/(s)eries$/i' => '\1\2eries', + '/([^aeiouy]|qu)ies$/i' => '\1y', + '/([lr])ves$/i' => '\1f', + '/(tive)s$/i' => '\1', + '/(hive)s$/i' => '\1', + '/(drive)s$/i' => '\1', + '/([^fo])ves$/i' => '\1fe', + '/(^analy)ses$/i' => '\1sis', + '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', + '/([ti])a$/i' => '\1um', + '/(p)eople$/i' => '\1\2erson', + '/(m)en$/i' => '\1an', + '/(c)hildren$/i' => '\1\2hild', + '/(f)eet$/i' => '\1oot', + '/(n)ews$/i' => '\1\2ews', + '/eaus$/' => 'eau', + '/^(.*us)$/' => '\\1', + '/s$/i' => '', + ), + 'uninflected' => array( + '.*[nrlm]ese', + '.*deer', + '.*fish', + '.*measles', + '.*ois', + '.*pox', + '.*sheep', + '.*ss', + ), + 'irregular' => array( + 'criteria' => 'criterion', + 'curves' => 'curve', + 'emphases' => 'emphasis', + 'foes' => 'foe', + 'hoaxes' => 'hoax', + 'media' => 'medium', + 'neuroses' => 'neurosis', + 'waves' => 'wave', + 'oases' => 'oasis', + ) + ); + + /** + * Words that should not be inflected. + * + * @var array + */ + private static $uninflected = array( + 'Amoyese', 'bison', 'Borghese', 'bream', 'breeches', 'britches', 'buffalo', 'cantus', + 'carp', 'chassis', 'clippers', 'cod', 'coitus', 'Congoese', 'contretemps', 'corps', + 'debris', 'diabetes', 'djinn', 'eland', 'elk', 'equipment', 'Faroese', 'flounder', + 'Foochowese', 'gallows', 'Genevese', 'Genoese', 'Gilbertese', 'graffiti', + 'headquarters', 'herpes', 'hijinks', 'Hottentotese', 'information', 'innings', + 'jackanapes', 'Kiplingese', 'Kongoese', 'Lucchese', 'mackerel', 'Maltese', '.*?media', + 'mews', 'moose', 'mumps', 'Nankingese', 'news', 'nexus', 'Niasese', + 'Pekingese', 'Piedmontese', 'pincers', 'Pistoiese', 'pliers', 'Portuguese', + 'proceedings', 'rabies', 'rice', 'rhinoceros', 'salmon', 'Sarawakese', 'scissors', + 'sea[- ]bass', 'series', 'Shavese', 'shears', 'siemens', 'species', 'staff', 'swine', + 'testes', 'trousers', 'trout', 'tuna', 'Vermontese', 'Wenchowese', 'whiting', + 'wildebeest', 'Yengeese' + ); + + /** + * Method cache array. + * + * @var array + */ + private static $cache = array(); + + /** + * The initial state of Inflector so reset() works. + * + * @var array + */ + private static $initialState = array(); + + /** + * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'. + * + * @param string $word The word to tableize. + * + * @return string The tableized word. + */ + public static function tableize($word) + { + return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word)); + } + + /** + * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'. + * + * @param string $word The word to classify. + * + * @return string The classified word. + */ + public static function classify($word) + { + return str_replace(" ", "", ucwords(strtr($word, "_-", " "))); + } + + /** + * Camelizes a word. This uses the classify() method and turns the first character to lowercase. + * + * @param string $word The word to camelize. + * + * @return string The camelized word. + */ + public static function camelize($word) + { + return lcfirst(self::classify($word)); + } + + /** + * Uppercases words with configurable delimeters between words. + * + * Takes a string and capitalizes all of the words, like PHP's built-in + * ucwords function. This extends that behavior, however, by allowing the + * word delimeters to be configured, rather than only separating on + * whitespace. + * + * Here is an example: + * + * + * + * + * @param string $string The string to operate on. + * @param string $delimiters A list of word separators. + * + * @return string The string with all delimeter-separated words capitalized. + */ + public static function ucwords($string, $delimiters = " \n\t\r\0\x0B-") + { + return preg_replace_callback( + '/[^' . preg_quote($delimiters, '/') . ']+/', + function($matches) { + return ucfirst($matches[0]); + }, + $string + ); + } + + /** + * Clears Inflectors inflected value caches, and resets the inflection + * rules to the initial values. + * + * @return void + */ + public static function reset() + { + if (empty(self::$initialState)) { + self::$initialState = get_class_vars('Inflector'); + + return; + } + + foreach (self::$initialState as $key => $val) { + if ($key != 'initialState') { + self::${$key} = $val; + } + } + } + + /** + * Adds custom inflection $rules, of either 'plural' or 'singular' $type. + * + * ### Usage: + * + * {{{ + * Inflector::rules('plural', array('/^(inflect)or$/i' => '\1ables')); + * Inflector::rules('plural', array( + * 'rules' => array('/^(inflect)ors$/i' => '\1ables'), + * 'uninflected' => array('dontinflectme'), + * 'irregular' => array('red' => 'redlings') + * )); + * }}} + * + * @param string $type The type of inflection, either 'plural' or 'singular' + * @param array $rules An array of rules to be added. + * @param boolean $reset If true, will unset default inflections for all + * new rules that are being defined in $rules. + * + * @return void + */ + public static function rules($type, $rules, $reset = false) + { + foreach ($rules as $rule => $pattern) { + if ( ! is_array($pattern)) { + continue; + } + + if ($reset) { + self::${$type}[$rule] = $pattern; + } else { + self::${$type}[$rule] = ($rule === 'uninflected') + ? array_merge($pattern, self::${$type}[$rule]) + : $pattern + self::${$type}[$rule]; + } + + unset($rules[$rule], self::${$type}['cache' . ucfirst($rule)]); + + if (isset(self::${$type}['merged'][$rule])) { + unset(self::${$type}['merged'][$rule]); + } + + if ($type === 'plural') { + self::$cache['pluralize'] = self::$cache['tableize'] = array(); + } elseif ($type === 'singular') { + self::$cache['singularize'] = array(); + } + } + + self::${$type}['rules'] = $rules + self::${$type}['rules']; + } + + /** + * Returns a word in plural form. + * + * @param string $word The word in singular form. + * + * @return string The word in plural form. + */ + public static function pluralize($word) + { + if (isset(self::$cache['pluralize'][$word])) { + return self::$cache['pluralize'][$word]; + } + + if (!isset(self::$plural['merged']['irregular'])) { + self::$plural['merged']['irregular'] = self::$plural['irregular']; + } + + if (!isset(self::$plural['merged']['uninflected'])) { + self::$plural['merged']['uninflected'] = array_merge(self::$plural['uninflected'], self::$uninflected); + } + + if (!isset(self::$plural['cacheUninflected']) || !isset(self::$plural['cacheIrregular'])) { + self::$plural['cacheUninflected'] = '(?:' . implode('|', self::$plural['merged']['uninflected']) . ')'; + self::$plural['cacheIrregular'] = '(?:' . implode('|', array_keys(self::$plural['merged']['irregular'])) . ')'; + } + + if (preg_match('/(.*)\\b(' . self::$plural['cacheIrregular'] . ')$/i', $word, $regs)) { + self::$cache['pluralize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$plural['merged']['irregular'][strtolower($regs[2])], 1); + + return self::$cache['pluralize'][$word]; + } + + if (preg_match('/^(' . self::$plural['cacheUninflected'] . ')$/i', $word, $regs)) { + self::$cache['pluralize'][$word] = $word; + + return $word; + } + + foreach (self::$plural['rules'] as $rule => $replacement) { + if (preg_match($rule, $word)) { + self::$cache['pluralize'][$word] = preg_replace($rule, $replacement, $word); + + return self::$cache['pluralize'][$word]; + } + } + } + + /** + * Returns a word in singular form. + * + * @param string $word The word in plural form. + * + * @return string The word in singular form. + */ + public static function singularize($word) + { + if (isset(self::$cache['singularize'][$word])) { + return self::$cache['singularize'][$word]; + } + + if (!isset(self::$singular['merged']['uninflected'])) { + self::$singular['merged']['uninflected'] = array_merge( + self::$singular['uninflected'], + self::$uninflected + ); + } + + if (!isset(self::$singular['merged']['irregular'])) { + self::$singular['merged']['irregular'] = array_merge( + self::$singular['irregular'], + array_flip(self::$plural['irregular']) + ); + } + + if (!isset(self::$singular['cacheUninflected']) || !isset(self::$singular['cacheIrregular'])) { + self::$singular['cacheUninflected'] = '(?:' . join('|', self::$singular['merged']['uninflected']) . ')'; + self::$singular['cacheIrregular'] = '(?:' . join('|', array_keys(self::$singular['merged']['irregular'])) . ')'; + } + + if (preg_match('/(.*)\\b(' . self::$singular['cacheIrregular'] . ')$/i', $word, $regs)) { + self::$cache['singularize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$singular['merged']['irregular'][strtolower($regs[2])], 1); + + return self::$cache['singularize'][$word]; + } + + if (preg_match('/^(' . self::$singular['cacheUninflected'] . ')$/i', $word, $regs)) { + self::$cache['singularize'][$word] = $word; + + return $word; + } + + foreach (self::$singular['rules'] as $rule => $replacement) { + if (preg_match($rule, $word)) { + self::$cache['singularize'][$word] = preg_replace($rule, $replacement, $word); + + return self::$cache['singularize'][$word]; + } + } + + self::$cache['singularize'][$word] = $word; + + return $word; + } +} diff --git a/vendor/doctrine/inflector/phpunit.xml.dist b/vendor/doctrine/inflector/phpunit.xml.dist new file mode 100644 index 00000000..ef07faa5 --- /dev/null +++ b/vendor/doctrine/inflector/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + ./tests/Doctrine/ + + + + + + ./lib/Doctrine/ + + + + + + performance + + + diff --git a/vendor/doctrine/inflector/tests/Doctrine/Tests/Common/Inflector/InflectorTest.php b/vendor/doctrine/inflector/tests/Doctrine/Tests/Common/Inflector/InflectorTest.php new file mode 100644 index 00000000..4198d22c --- /dev/null +++ b/vendor/doctrine/inflector/tests/Doctrine/Tests/Common/Inflector/InflectorTest.php @@ -0,0 +1,309 @@ +assertEquals( + $singular, + Inflector::singularize($plural), + "'$plural' should be singularized to '$singular'" + ); + } + + /** + * testInflectingPlurals method + * + * @dataProvider dataSampleWords + * @return void + */ + public function testInflectingPlurals($singular, $plural) + { + $this->assertEquals( + $plural, + Inflector::pluralize($singular), + "'$singular' should be pluralized to '$plural'" + ); + } + + /** + * testCustomPluralRule method + * + * @return void + */ + public function testCustomPluralRule() + { + Inflector::reset(); + Inflector::rules('plural', array('/^(custom)$/i' => '\1izables')); + + $this->assertEquals(Inflector::pluralize('custom'), 'customizables'); + + Inflector::rules('plural', array('uninflected' => array('uninflectable'))); + + $this->assertEquals(Inflector::pluralize('uninflectable'), 'uninflectable'); + + Inflector::rules('plural', array( + 'rules' => array('/^(alert)$/i' => '\1ables'), + 'uninflected' => array('noflect', 'abtuse'), + 'irregular' => array('amaze' => 'amazable', 'phone' => 'phonezes') + )); + + $this->assertEquals(Inflector::pluralize('noflect'), 'noflect'); + $this->assertEquals(Inflector::pluralize('abtuse'), 'abtuse'); + $this->assertEquals(Inflector::pluralize('alert'), 'alertables'); + $this->assertEquals(Inflector::pluralize('amaze'), 'amazable'); + $this->assertEquals(Inflector::pluralize('phone'), 'phonezes'); + } + + /** + * testCustomSingularRule method + * + * @return void + */ + public function testCustomSingularRule() + { + Inflector::reset(); + Inflector::rules('singular', array('/(eple)r$/i' => '\1', '/(jente)r$/i' => '\1')); + + $this->assertEquals(Inflector::singularize('epler'), 'eple'); + $this->assertEquals(Inflector::singularize('jenter'), 'jente'); + + Inflector::rules('singular', array( + 'rules' => array('/^(bil)er$/i' => '\1', '/^(inflec|contribu)tors$/i' => '\1ta'), + 'uninflected' => array('singulars'), + 'irregular' => array('spins' => 'spinor') + )); + + $this->assertEquals(Inflector::singularize('inflectors'), 'inflecta'); + $this->assertEquals(Inflector::singularize('contributors'), 'contributa'); + $this->assertEquals(Inflector::singularize('spins'), 'spinor'); + $this->assertEquals(Inflector::singularize('singulars'), 'singulars'); + } + + /** + * test that setting new rules clears the inflector caches. + * + * @return void + */ + public function testRulesClearsCaches() + { + Inflector::reset(); + + $this->assertEquals(Inflector::singularize('Bananas'), 'Banana'); + $this->assertEquals(Inflector::pluralize('Banana'), 'Bananas'); + + Inflector::rules('singular', array( + 'rules' => array('/(.*)nas$/i' => '\1zzz') + )); + + $this->assertEquals('Banazzz', Inflector::singularize('Bananas'), 'Was inflected with old rules.'); + + Inflector::rules('plural', array( + 'rules' => array('/(.*)na$/i' => '\1zzz'), + 'irregular' => array('corpus' => 'corpora') + )); + + $this->assertEquals(Inflector::pluralize('Banana'), 'Banazzz', 'Was inflected with old rules.'); + $this->assertEquals(Inflector::pluralize('corpus'), 'corpora', 'Was inflected with old irregular form.'); + } + + /** + * Test resetting inflection rules. + * + * @return void + */ + public function testCustomRuleWithReset() + { + Inflector::reset(); + + $uninflected = array('atlas', 'lapis', 'onibus', 'pires', 'virus', '.*x'); + $pluralIrregular = array('as' => 'ases'); + + Inflector::rules('singular', array( + 'rules' => array('/^(.*)(a|e|o|u)is$/i' => '\1\2l'), + 'uninflected' => $uninflected, + ), true); + + Inflector::rules('plural', array( + 'rules' => array( + '/^(.*)(a|e|o|u)l$/i' => '\1\2is', + ), + 'uninflected' => $uninflected, + 'irregular' => $pluralIrregular + ), true); + + $this->assertEquals(Inflector::pluralize('Alcool'), 'Alcoois'); + $this->assertEquals(Inflector::pluralize('Atlas'), 'Atlas'); + $this->assertEquals(Inflector::singularize('Alcoois'), 'Alcool'); + $this->assertEquals(Inflector::singularize('Atlas'), 'Atlas'); + } + + /** + * Test basic ucwords functionality. + * + * @return void + */ + public function testUcwords() + { + $this->assertSame('Top-O-The-Morning To All_of_you!', Inflector::ucwords( 'top-o-the-morning to all_of_you!')); + } + + /** + * Test ucwords functionality with custom delimeters. + * + * @return void + */ + public function testUcwordsWithCustomDelimeters() + { + $this->assertSame('Top-O-The-Morning To All_Of_You!', Inflector::ucwords( 'top-o-the-morning to all_of_you!', '-_ ')); + } +} + diff --git a/vendor/doctrine/inflector/tests/Doctrine/Tests/DoctrineTestCase.php b/vendor/doctrine/inflector/tests/Doctrine/Tests/DoctrineTestCase.php new file mode 100644 index 00000000..e8323d29 --- /dev/null +++ b/vendor/doctrine/inflector/tests/Doctrine/Tests/DoctrineTestCase.php @@ -0,0 +1,10 @@ +=5.3.2" + }, + "autoload": { + "psr-0": { "Doctrine\\Common\\Lexer\\": "lib/" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php b/vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php new file mode 100644 index 00000000..399a5523 --- /dev/null +++ b/vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php @@ -0,0 +1,327 @@ +. + */ + +namespace Doctrine\Common\Lexer; + +/** + * Base class for writing simple lexers, i.e. for creating small DSLs. + * + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +abstract class AbstractLexer +{ + /** + * Lexer original input string. + * + * @var string + */ + private $input; + + /** + * Array of scanned tokens. + * + * Each token is an associative array containing three items: + * - 'value' : the string value of the token in the input string + * - 'type' : the type of the token (identifier, numeric, string, input + * parameter, none) + * - 'position' : the position of the token in the input string + * + * @var array + */ + private $tokens = array(); + + /** + * Current lexer position in input string. + * + * @var integer + */ + private $position = 0; + + /** + * Current peek of current lexer position. + * + * @var integer + */ + private $peek = 0; + + /** + * The next token in the input. + * + * @var array + */ + public $lookahead; + + /** + * The last matched/seen token. + * + * @var array + */ + public $token; + + /** + * Sets the input data to be tokenized. + * + * The Lexer is immediately reset and the new input tokenized. + * Any unprocessed tokens from any previous input are lost. + * + * @param string $input The input to be tokenized. + * + * @return void + */ + public function setInput($input) + { + $this->input = $input; + $this->tokens = array(); + + $this->reset(); + $this->scan($input); + } + + /** + * Resets the lexer. + * + * @return void + */ + public function reset() + { + $this->lookahead = null; + $this->token = null; + $this->peek = 0; + $this->position = 0; + } + + /** + * Resets the peek pointer to 0. + * + * @return void + */ + public function resetPeek() + { + $this->peek = 0; + } + + /** + * Resets the lexer position on the input to the given position. + * + * @param integer $position Position to place the lexical scanner. + * + * @return void + */ + public function resetPosition($position = 0) + { + $this->position = $position; + } + + /** + * Retrieve the original lexer's input until a given position. + * + * @param integer $position + * + * @return string + */ + public function getInputUntilPosition($position) + { + return substr($this->input, 0, $position); + } + + /** + * Checks whether a given token matches the current lookahead. + * + * @param integer|string $token + * + * @return boolean + */ + public function isNextToken($token) + { + return null !== $this->lookahead && $this->lookahead['type'] === $token; + } + + /** + * Checks whether any of the given tokens matches the current lookahead. + * + * @param array $tokens + * + * @return boolean + */ + public function isNextTokenAny(array $tokens) + { + return null !== $this->lookahead && in_array($this->lookahead['type'], $tokens, true); + } + + /** + * Moves to the next token in the input string. + * + * @return boolean + */ + public function moveNext() + { + $this->peek = 0; + $this->token = $this->lookahead; + $this->lookahead = (isset($this->tokens[$this->position])) + ? $this->tokens[$this->position++] : null; + + return $this->lookahead !== null; + } + + /** + * Tells the lexer to skip input tokens until it sees a token with the given value. + * + * @param string $type The token type to skip until. + * + * @return void + */ + public function skipUntil($type) + { + while ($this->lookahead !== null && $this->lookahead['type'] !== $type) { + $this->moveNext(); + } + } + + /** + * Checks if given value is identical to the given token. + * + * @param mixed $value + * @param integer $token + * + * @return boolean + */ + public function isA($value, $token) + { + return $this->getType($value) === $token; + } + + /** + * Moves the lookahead token forward. + * + * @return array|null The next token or NULL if there are no more tokens ahead. + */ + public function peek() + { + if (isset($this->tokens[$this->position + $this->peek])) { + return $this->tokens[$this->position + $this->peek++]; + } else { + return null; + } + } + + /** + * Peeks at the next token, returns it and immediately resets the peek. + * + * @return array|null The next token or NULL if there are no more tokens ahead. + */ + public function glimpse() + { + $peek = $this->peek(); + $this->peek = 0; + return $peek; + } + + /** + * Scans the input string for tokens. + * + * @param string $input A query string. + * + * @return void + */ + protected function scan($input) + { + static $regex; + + if ( ! isset($regex)) { + $regex = sprintf( + '/(%s)|%s/%s', + implode(')|(', $this->getCatchablePatterns()), + implode('|', $this->getNonCatchablePatterns()), + $this->getModifiers() + ); + } + + $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; + $matches = preg_split($regex, $input, -1, $flags); + + foreach ($matches as $match) { + // Must remain before 'value' assignment since it can change content + $type = $this->getType($match[0]); + + $this->tokens[] = array( + 'value' => $match[0], + 'type' => $type, + 'position' => $match[1], + ); + } + } + + /** + * Gets the literal for a given token. + * + * @param integer $token + * + * @return string + */ + public function getLiteral($token) + { + $className = get_class($this); + $reflClass = new \ReflectionClass($className); + $constants = $reflClass->getConstants(); + + foreach ($constants as $name => $value) { + if ($value === $token) { + return $className . '::' . $name; + } + } + + return $token; + } + + /** + * Regex modifiers + * + * @return string + */ + protected function getModifiers() + { + return 'i'; + } + + /** + * Lexical catchable patterns. + * + * @return array + */ + abstract protected function getCatchablePatterns(); + + /** + * Lexical non-catchable patterns. + * + * @return array + */ + abstract protected function getNonCatchablePatterns(); + + /** + * Retrieve token type. Also processes the token value if necessary. + * + * @param string $value + * + * @return integer + */ + abstract protected function getType(&$value); +} diff --git a/vendor/monolog/monolog/.php_cs b/vendor/monolog/monolog/.php_cs new file mode 100644 index 00000000..366ccd08 --- /dev/null +++ b/vendor/monolog/monolog/.php_cs @@ -0,0 +1,59 @@ + + +For the full copyright and license information, please view the LICENSE +file that was distributed with this source code. +EOF; + +$finder = Symfony\CS\Finder::create() + ->files() + ->name('*.php') + ->exclude('Fixtures') + ->in(__DIR__.'/src') + ->in(__DIR__.'/tests') +; + +return Symfony\CS\Config::create() + ->setUsingCache(true) + //->setUsingLinter(false) + ->setRiskyAllowed(true) + ->setRules(array( + '@PSR2' => true, + 'binary_operator_spaces' => true, + 'blank_line_before_return' => true, + 'header_comment' => array('header' => $header), + 'include' => true, + 'long_array_syntax' => true, + 'method_separation' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_blank_lines_between_uses' => true, + 'no_duplicate_semicolons' => true, + 'no_extra_consecutive_blank_lines' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_unused_imports' => true, + 'object_operator_without_whitespace' => true, + 'phpdoc_align' => true, + 'phpdoc_indent' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_package' => true, + 'phpdoc_order' => true, + 'phpdoc_scalar' => true, + 'phpdoc_trim' => true, + 'phpdoc_type_to_var' => true, + 'psr0' => true, + 'single_blank_line_before_namespace' => true, + 'spaces_cast' => true, + 'standardize_not_equals' => true, + 'ternary_operator_spaces' => true, + 'trailing_comma_in_multiline_array' => true, + 'whitespacy_lines' => true, + )) + ->finder($finder) +; diff --git a/vendor/monolog/monolog/CHANGELOG.md b/vendor/monolog/monolog/CHANGELOG.md new file mode 100644 index 00000000..1153c62c --- /dev/null +++ b/vendor/monolog/monolog/CHANGELOG.md @@ -0,0 +1,333 @@ +### 1.22.1 (2017-03-13) + + * Fixed lots of minor issues in the new Slack integrations + * Fixed support for allowInlineLineBreaks in LineFormatter when formatting exception backtraces + +### 1.22.0 (2016-11-26) + + * Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily + * Added MercurialProcessor to add mercurial revision and branch names to log records + * Added support for AWS SDK v3 in DynamoDbHandler + * Fixed fatal errors occuring when normalizing generators that have been fully consumed + * Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix) + * Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore + * Fixed SyslogUdpHandler to avoid sending empty frames + * Fixed a few PHP 7.0 and 7.1 compatibility issues + +### 1.21.0 (2016-07-29) + + * Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues + * Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order + * Added ability to format the main line of text the SlackHandler sends by explictly setting a formatter on the handler + * Added information about SoapFault instances in NormalizerFormatter + * Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level + +### 1.20.0 (2016-07-02) + + * Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy + * Added StreamHandler::getUrl to retrieve the stream's URL + * Added ability to override addRow/addTitle in HtmlFormatter + * Added the $context to context information when the ErrorHandler handles a regular php error + * Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d + * Fixed WhatFailureGroupHandler to work with PHP7 throwables + * Fixed a few minor bugs + +### 1.19.0 (2016-04-12) + + * Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed + * Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors + * Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler + * Fixed HipChatHandler handling of long messages + +### 1.18.2 (2016-04-02) + + * Fixed ElasticaFormatter to use more precise dates + * Fixed GelfMessageFormatter sending too long messages + +### 1.18.1 (2016-03-13) + + * Fixed SlackHandler bug where slack dropped messages randomly + * Fixed RedisHandler issue when using with the PHPRedis extension + * Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension + * Fixed BrowserConsoleHandler regression + +### 1.18.0 (2016-03-01) + + * Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond + * Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames + * Added `Logger->withName` to clone a logger (keeping all handlers) with a new name + * Added FluentdFormatter for the Fluentd unix socket protocol + * Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed + * Added support for replacing context sub-keys using `%context.*%` in LineFormatter + * Added support for `payload` context value in RollbarHandler + * Added setRelease to RavenHandler to describe the application version, sent with every log + * Added support for `fingerprint` context value in RavenHandler + * Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed + * Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()` + * Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places + +### 1.17.2 (2015-10-14) + + * Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers + * Fixed SlackHandler handling to use slack functionalities better + * Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id + * Fixed 5.3 compatibility regression + +### 1.17.1 (2015-08-31) + + * Fixed RollbarHandler triggering PHP notices + +### 1.17.0 (2015-08-30) + + * Added support for `checksum` and `release` context/extra values in RavenHandler + * Added better support for exceptions in RollbarHandler + * Added UidProcessor::getUid + * Added support for showing the resource type in NormalizedFormatter + * Fixed IntrospectionProcessor triggering PHP notices + +### 1.16.0 (2015-08-09) + + * Added IFTTTHandler to notify ifttt.com triggers + * Added Logger::setHandlers() to allow setting/replacing all handlers + * Added $capSize in RedisHandler to cap the log size + * Fixed StreamHandler creation of directory to only trigger when the first log write happens + * Fixed bug in the handling of curl failures + * Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler + * Fixed missing fatal errors records with handlers that need to be closed to flush log records + * Fixed TagProcessor::addTags support for associative arrays + +### 1.15.0 (2015-07-12) + + * Added addTags and setTags methods to change a TagProcessor + * Added automatic creation of directories if they are missing for a StreamHandler to open a log file + * Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure + * Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used + * Fixed HTML/JS escaping in BrowserConsoleHandler + * Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only) + +### 1.14.0 (2015-06-19) + + * Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library + * Added support for objects implementing __toString in the NormalizerFormatter + * Added support for HipChat's v2 API in HipChatHandler + * Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app + * Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true) + * Fixed curl errors being silently suppressed + +### 1.13.1 (2015-03-09) + + * Fixed regression in HipChat requiring a new token to be created + +### 1.13.0 (2015-03-05) + + * Added Registry::hasLogger to check for the presence of a logger instance + * Added context.user support to RavenHandler + * Added HipChat API v2 support in the HipChatHandler + * Added NativeMailerHandler::addParameter to pass params to the mail() process + * Added context data to SlackHandler when $includeContextAndExtra is true + * Added ability to customize the Swift_Message per-email in SwiftMailerHandler + * Fixed SwiftMailerHandler to lazily create message instances if a callback is provided + * Fixed serialization of INF and NaN values in Normalizer and LineFormatter + +### 1.12.0 (2014-12-29) + + * Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers. + * Added PsrHandler to forward records to another PSR-3 logger + * Added SamplingHandler to wrap around a handler and include only every Nth record + * Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now) + * Added exception codes in the output of most formatters + * Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line) + * Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data + * Added $host to HipChatHandler for users of private instances + * Added $transactionName to NewRelicHandler and support for a transaction_name context value + * Fixed MandrillHandler to avoid outputing API call responses + * Fixed some non-standard behaviors in SyslogUdpHandler + +### 1.11.0 (2014-09-30) + + * Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names + * Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails + * Added MandrillHandler to send emails via the Mandrillapp.com API + * Added SlackHandler to log records to a Slack.com account + * Added FleepHookHandler to log records to a Fleep.io account + * Added LogglyHandler::addTag to allow adding tags to an existing handler + * Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end + * Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing + * Added support for PhpAmqpLib in the AmqpHandler + * Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs + * Added support for adding extra fields from $_SERVER in the WebProcessor + * Fixed support for non-string values in PrsLogMessageProcessor + * Fixed SwiftMailer messages being sent with the wrong date in long running scripts + * Fixed minor PHP 5.6 compatibility issues + * Fixed BufferHandler::close being called twice + +### 1.10.0 (2014-06-04) + + * Added Logger::getHandlers() and Logger::getProcessors() methods + * Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached + * Added support for extra data in NewRelicHandler + * Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines + +### 1.9.1 (2014-04-24) + + * Fixed regression in RotatingFileHandler file permissions + * Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records + * Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative + +### 1.9.0 (2014-04-20) + + * Added LogEntriesHandler to send logs to a LogEntries account + * Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler + * Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes + * Added support for table formatting in FirePHPHandler via the table context key + * Added a TagProcessor to add tags to records, and support for tags in RavenHandler + * Added $appendNewline flag to the JsonFormatter to enable using it when logging to files + * Added sound support to the PushoverHandler + * Fixed multi-threading support in StreamHandler + * Fixed empty headers issue when ChromePHPHandler received no records + * Fixed default format of the ErrorLogHandler + +### 1.8.0 (2014-03-23) + + * Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them + * Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output + * Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler + * Added FlowdockHandler to send logs to a Flowdock account + * Added RollbarHandler to send logs to a Rollbar account + * Added HtmlFormatter to send prettier log emails with colors for each log level + * Added GitProcessor to add the current branch/commit to extra record data + * Added a Monolog\Registry class to allow easier global access to pre-configured loggers + * Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement + * Added support for HHVM + * Added support for Loggly batch uploads + * Added support for tweaking the content type and encoding in NativeMailerHandler + * Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor + * Fixed batch request support in GelfHandler + +### 1.7.0 (2013-11-14) + + * Added ElasticSearchHandler to send logs to an Elastic Search server + * Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB + * Added SyslogUdpHandler to send logs to a remote syslogd server + * Added LogglyHandler to send logs to a Loggly account + * Added $level to IntrospectionProcessor so it only adds backtraces when needed + * Added $version to LogstashFormatter to allow using the new v1 Logstash format + * Added $appName to NewRelicHandler + * Added configuration of Pushover notification retries/expiry + * Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default + * Added chainability to most setters for all handlers + * Fixed RavenHandler batch processing so it takes the message from the record with highest priority + * Fixed HipChatHandler batch processing so it sends all messages at once + * Fixed issues with eAccelerator + * Fixed and improved many small things + +### 1.6.0 (2013-07-29) + + * Added HipChatHandler to send logs to a HipChat chat room + * Added ErrorLogHandler to send logs to PHP's error_log function + * Added NewRelicHandler to send logs to NewRelic's service + * Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler + * Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel + * Added stack traces output when normalizing exceptions (json output & co) + * Added Monolog\Logger::API constant (currently 1) + * Added support for ChromePHP's v4.0 extension + * Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel + * Added support for sending messages to multiple users at once with the PushoverHandler + * Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler) + * Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now + * Fixed issue in RotatingFileHandler when an open_basedir restriction is active + * Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0 + * Fixed SyslogHandler issue when many were used concurrently with different facilities + +### 1.5.0 (2013-04-23) + + * Added ProcessIdProcessor to inject the PID in log records + * Added UidProcessor to inject a unique identifier to all log records of one request/run + * Added support for previous exceptions in the LineFormatter exception serialization + * Added Monolog\Logger::getLevels() to get all available levels + * Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle + +### 1.4.1 (2013-04-01) + + * Fixed exception formatting in the LineFormatter to be more minimalistic + * Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0 + * Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days + * Fixed WebProcessor array access so it checks for data presence + * Fixed Buffer, Group and FingersCrossed handlers to make use of their processors + +### 1.4.0 (2013-02-13) + + * Added RedisHandler to log to Redis via the Predis library or the phpredis extension + * Added ZendMonitorHandler to log to the Zend Server monitor + * Added the possibility to pass arrays of handlers and processors directly in the Logger constructor + * Added `$useSSL` option to the PushoverHandler which is enabled by default + * Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously + * Fixed header injection capability in the NativeMailHandler + +### 1.3.1 (2013-01-11) + + * Fixed LogstashFormatter to be usable with stream handlers + * Fixed GelfMessageFormatter levels on Windows + +### 1.3.0 (2013-01-08) + + * Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface` + * Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance + * Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash) + * Added PushoverHandler to send mobile notifications + * Added CouchDBHandler and DoctrineCouchDBHandler + * Added RavenHandler to send data to Sentry servers + * Added support for the new MongoClient class in MongoDBHandler + * Added microsecond precision to log records' timestamps + * Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing + the oldest entries + * Fixed normalization of objects with cyclic references + +### 1.2.1 (2012-08-29) + + * Added new $logopts arg to SyslogHandler to provide custom openlog options + * Fixed fatal error in SyslogHandler + +### 1.2.0 (2012-08-18) + + * Added AmqpHandler (for use with AMQP servers) + * Added CubeHandler + * Added NativeMailerHandler::addHeader() to send custom headers in mails + * Added the possibility to specify more than one recipient in NativeMailerHandler + * Added the possibility to specify float timeouts in SocketHandler + * Added NOTICE and EMERGENCY levels to conform with RFC 5424 + * Fixed the log records to use the php default timezone instead of UTC + * Fixed BufferHandler not being flushed properly on PHP fatal errors + * Fixed normalization of exotic resource types + * Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog + +### 1.1.0 (2012-04-23) + + * Added Monolog\Logger::isHandling() to check if a handler will + handle the given log level + * Added ChromePHPHandler + * Added MongoDBHandler + * Added GelfHandler (for use with Graylog2 servers) + * Added SocketHandler (for use with syslog-ng for example) + * Added NormalizerFormatter + * Added the possibility to change the activation strategy of the FingersCrossedHandler + * Added possibility to show microseconds in logs + * Added `server` and `referer` to WebProcessor output + +### 1.0.2 (2011-10-24) + + * Fixed bug in IE with large response headers and FirePHPHandler + +### 1.0.1 (2011-08-25) + + * Added MemoryPeakUsageProcessor and MemoryUsageProcessor + * Added Monolog\Logger::getName() to get a logger's channel name + +### 1.0.0 (2011-07-06) + + * Added IntrospectionProcessor to get info from where the logger was called + * Fixed WebProcessor in CLI + +### 1.0.0-RC1 (2011-07-01) + + * Initial release diff --git a/vendor/monolog/monolog/LICENSE b/vendor/monolog/monolog/LICENSE new file mode 100644 index 00000000..16473219 --- /dev/null +++ b/vendor/monolog/monolog/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2016 Jordi Boggiano + +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/vendor/monolog/monolog/README.md b/vendor/monolog/monolog/README.md new file mode 100644 index 00000000..7d8ade52 --- /dev/null +++ b/vendor/monolog/monolog/README.md @@ -0,0 +1,95 @@ +# Monolog - Logging for PHP [![Build Status](https://img.shields.io/travis/Seldaek/monolog.svg)](https://travis-ci.org/Seldaek/monolog) + +[![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) +[![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) +[![Reference Status](https://www.versioneye.com/php/monolog:monolog/reference_badge.svg)](https://www.versioneye.com/php/monolog:monolog/references) + + +Monolog sends your logs to files, sockets, inboxes, databases and various +web services. See the complete list of handlers below. Special handlers +allow you to build advanced logging strategies. + +This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +interface that you can type-hint against in your own libraries to keep +a maximum of interoperability. You can also use it in your applications to +make sure you can always use another compatible logger at a later time. +As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels. +Internally Monolog still uses its own level scheme since it predates PSR-3. + +## Installation + +Install the latest version with + +```bash +$ composer require monolog/monolog +``` + +## Basic Usage + +```php +pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); + +// add records to the log +$log->addWarning('Foo'); +$log->addError('Bar'); +``` + +## Documentation + +- [Usage Instructions](doc/01-usage.md) +- [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md) +- [Utility classes](doc/03-utilities.md) +- [Extending Monolog](doc/04-extending.md) + +## Third Party Packages + +Third party handlers, formatters and processors are +[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You +can also add your own there if you publish one. + +## About + +### Requirements + +- Monolog works with PHP 5.3 or above, and is also tested to work with HHVM. + +### Submitting bugs and feature requests + +Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues) + +### Framework Integrations + +- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) + can be used very easily with Monolog since it implements the interface. +- [Symfony2](http://symfony.com) comes out of the box with Monolog. +- [Silex](http://silex.sensiolabs.org/) comes out of the box with Monolog. +- [Laravel 4 & 5](http://laravel.com/) come out of the box with Monolog. +- [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog. +- [PPI](http://www.ppi.io/) comes out of the box with Monolog. +- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin. +- [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer. +- [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog. +- [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog. +- [Nette Framework](http://nette.org/en/) can be used with Monolog via [Kdyby/Monolog](https://github.com/Kdyby/Monolog) extension. +- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog. + +### Author + +Jordi Boggiano - -
+See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) which participated in this project. + +### License + +Monolog is licensed under the MIT License - see the `LICENSE` file for details + +### Acknowledgements + +This library is heavily inspired by Python's [Logbook](http://packages.python.org/Logbook/) +library, although most concepts have been adjusted to fit to the PHP world. diff --git a/vendor/monolog/monolog/composer.json b/vendor/monolog/monolog/composer.json new file mode 100644 index 00000000..f74b7b97 --- /dev/null +++ b/vendor/monolog/monolog/composer.json @@ -0,0 +1,66 @@ +{ + "name": "monolog/monolog", + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "keywords": ["log", "logging", "psr-3"], + "homepage": "http://github.com/Seldaek/monolog", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.5", + "graylog2/gelf-php": "~1.0", + "sentry/sentry": "^0.13", + "ruflin/elastica": ">=0.90 <3.0", + "doctrine/couchdb": "~1.0@dev", + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "php-amqplib/php-amqplib": "~2.4", + "swiftmailer/swiftmailer": "~5.3", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit-mock-objects": "2.3.0", + "jakub-onderka/php-parallel-lint": "0.9" + }, + "_": "phpunit/phpunit-mock-objects required in 2.3.0 due to https://github.com/sebastianbergmann/phpunit-mock-objects/issues/223 - needs hhvm 3.8+ on travis", + "suggest": { + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "sentry/sentry": "Allow sending log messages to a Sentry server", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "php-console/php-console": "Allow sending log messages to Google Chrome" + }, + "autoload": { + "psr-4": {"Monolog\\": "src/Monolog"} + }, + "autoload-dev": { + "psr-4": {"Monolog\\": "tests/Monolog"} + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "scripts": { + "test": [ + "parallel-lint . --exclude vendor", + "phpunit" + ] + } +} diff --git a/vendor/monolog/monolog/doc/01-usage.md b/vendor/monolog/monolog/doc/01-usage.md new file mode 100644 index 00000000..8e2551f3 --- /dev/null +++ b/vendor/monolog/monolog/doc/01-usage.md @@ -0,0 +1,231 @@ +# Using Monolog + +- [Installation](#installation) +- [Core Concepts](#core-concepts) +- [Log Levels](#log-levels) +- [Configuring a logger](#configuring-a-logger) +- [Adding extra data in the records](#adding-extra-data-in-the-records) +- [Leveraging channels](#leveraging-channels) +- [Customizing the log format](#customizing-the-log-format) + +## Installation + +Monolog is available on Packagist ([monolog/monolog](http://packagist.org/packages/monolog/monolog)) +and as such installable via [Composer](http://getcomposer.org/). + +```bash +composer require monolog/monolog +``` + +If you do not use Composer, you can grab the code from GitHub, and use any +PSR-0 compatible autoloader (e.g. the [Symfony2 ClassLoader component](https://github.com/symfony/ClassLoader)) +to load Monolog classes. + +## Core Concepts + +Every `Logger` instance has a channel (name) and a stack of handlers. Whenever +you add a record to the logger, it traverses the handler stack. Each handler +decides whether it fully handled the record, and if so, the propagation of the +record ends there. + +This allows for flexible logging setups, for example having a `StreamHandler` at +the bottom of the stack that will log anything to disk, and on top of that add +a `MailHandler` that will send emails only when an error message is logged. +Handlers also have a `$bubble` property which defines whether they block the +record or not if they handled it. In this example, setting the `MailHandler`'s +`$bubble` argument to false means that records handled by the `MailHandler` will +not propagate to the `StreamHandler` anymore. + +You can create many `Logger`s, each defining a channel (e.g.: db, request, +router, ..) and each of them combining various handlers, which can be shared +or not. The channel is reflected in the logs and allows you to easily see or +filter records. + +Each Handler also has a Formatter, a default one with settings that make sense +will be created if you don't set one. The formatters normalize and format +incoming records so that they can be used by the handlers to output useful +information. + +Custom severity levels are not available. Only the eight +[RFC 5424](http://tools.ietf.org/html/rfc5424) levels (debug, info, notice, +warning, error, critical, alert, emergency) are present for basic filtering +purposes, but for sorting and other use cases that would require +flexibility, you should add Processors to the Logger that can add extra +information (tags, user ip, ..) to the records before they are handled. + +## Log Levels + +Monolog supports the logging levels described by [RFC 5424](http://tools.ietf.org/html/rfc5424). + +- **DEBUG** (100): Detailed debug information. + +- **INFO** (200): Interesting events. Examples: User logs in, SQL logs. + +- **NOTICE** (250): Normal but significant events. + +- **WARNING** (300): Exceptional occurrences that are not errors. Examples: + Use of deprecated APIs, poor use of an API, undesirable things that are not + necessarily wrong. + +- **ERROR** (400): Runtime errors that do not require immediate action but + should typically be logged and monitored. + +- **CRITICAL** (500): Critical conditions. Example: Application component + unavailable, unexpected exception. + +- **ALERT** (550): Action must be taken immediately. Example: Entire website + down, database unavailable, etc. This should trigger the SMS alerts and wake + you up. + +- **EMERGENCY** (600): Emergency: system is unusable. + +## Configuring a logger + +Here is a basic setup to log to a file and to firephp on the DEBUG level: + +```php +pushHandler(new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG)); +$logger->pushHandler(new FirePHPHandler()); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); +``` + +Let's explain it. The first step is to create the logger instance which will +be used in your code. The argument is a channel name, which is useful when +you use several loggers (see below for more details about it). + +The logger itself does not know how to handle a record. It delegates it to +some handlers. The code above registers two handlers in the stack to allow +handling records in two different ways. + +Note that the FirePHPHandler is called first as it is added on top of the +stack. This allows you to temporarily add a logger with bubbling disabled if +you want to override other configured loggers. + +> If you use Monolog standalone and are looking for an easy way to +> configure many handlers, the [theorchard/monolog-cascade](https://github.com/theorchard/monolog-cascade) +> can help you build complex logging configs via PHP arrays, yaml or json configs. + +## Adding extra data in the records + +Monolog provides two different ways to add extra informations along the simple +textual message. + +### Using the logging context + +The first way is the context, allowing to pass an array of data along the +record: + +```php +addInfo('Adding a new user', array('username' => 'Seldaek')); +``` + +Simple handlers (like the StreamHandler for instance) will simply format +the array to a string but richer handlers can take advantage of the context +(FirePHP is able to display arrays in pretty way for instance). + +### Using processors + +The second way is to add extra data for all records by using a processor. +Processors can be any callable. They will get the record as parameter and +must return it after having eventually changed the `extra` part of it. Let's +write a processor adding some dummy data in the record: + +```php +pushProcessor(function ($record) { + $record['extra']['dummy'] = 'Hello world!'; + + return $record; +}); +``` + +Monolog provides some built-in processors that can be used in your project. +Look at the [dedicated chapter](https://github.com/Seldaek/monolog/blob/master/doc/02-handlers-formatters-processors.md#processors) for the list. + +> Tip: processors can also be registered on a specific handler instead of + the logger to apply only for this handler. + +## Leveraging channels + +Channels are a great way to identify to which part of the application a record +is related. This is useful in big applications (and is leveraged by +MonologBundle in Symfony2). + +Picture two loggers sharing a handler that writes to a single log file. +Channels would allow you to identify the logger that issued every record. +You can easily grep through the log files filtering this or that channel. + +```php +pushHandler($stream); +$logger->pushHandler($firephp); + +// Create a logger for the security-related stuff with a different channel +$securityLogger = new Logger('security'); +$securityLogger->pushHandler($stream); +$securityLogger->pushHandler($firephp); + +// Or clone the first one to only change the channel +$securityLogger = $logger->withName('security'); +``` + +## Customizing the log format + +In Monolog it's easy to customize the format of the logs written into files, +sockets, mails, databases and other handlers. Most of the handlers use the + +```php +$record['formatted'] +``` + +value to be automatically put into the log device. This value depends on the +formatter settings. You can choose between predefined formatter classes or +write your own (e.g. a multiline text file for human-readable output). + +To configure a predefined formatter class, just set it as the handler's field: + +```php +// the default date format is "Y-m-d H:i:s" +$dateFormat = "Y n j, g:i a"; +// the default output format is "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n" +$output = "%datetime% > %level_name% > %message% %context% %extra%\n"; +// finally, create a formatter +$formatter = new LineFormatter($output, $dateFormat); + +// Create a handler +$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG); +$stream->setFormatter($formatter); +// bind it to a logger object +$securityLogger = new Logger('security'); +$securityLogger->pushHandler($stream); +``` + +You may also reuse the same formatter between multiple handlers and share those +handlers between multiple loggers. + +[Handlers, Formatters and Processors](02-handlers-formatters-processors.md) → diff --git a/vendor/monolog/monolog/doc/02-handlers-formatters-processors.md b/vendor/monolog/monolog/doc/02-handlers-formatters-processors.md new file mode 100644 index 00000000..bea968ac --- /dev/null +++ b/vendor/monolog/monolog/doc/02-handlers-formatters-processors.md @@ -0,0 +1,157 @@ +# Handlers, Formatters and Processors + +- [Handlers](#handlers) + - [Log to files and syslog](#log-to-files-and-syslog) + - [Send alerts and emails](#send-alerts-and-emails) + - [Log specific servers and networked logging](#log-specific-servers-and-networked-logging) + - [Logging in development](#logging-in-development) + - [Log to databases](#log-to-databases) + - [Wrappers / Special Handlers](#wrappers--special-handlers) +- [Formatters](#formatters) +- [Processors](#processors) +- [Third Party Packages](#third-party-packages) + +## Handlers + +### Log to files and syslog + +- _StreamHandler_: Logs records into any PHP stream, use this for log files. +- _RotatingFileHandler_: Logs records to a file and creates one logfile per day. + It will also delete files older than `$maxFiles`. You should use + [logrotate](http://linuxcommand.org/man_pages/logrotate8.html) for high profile + setups though, this is just meant as a quick and dirty solution. +- _SyslogHandler_: Logs records to the syslog. +- _ErrorLogHandler_: Logs records to PHP's + [`error_log()`](http://docs.php.net/manual/en/function.error-log.php) function. + +### Send alerts and emails + +- _NativeMailerHandler_: Sends emails using PHP's + [`mail()`](http://php.net/manual/en/function.mail.php) function. +- _SwiftMailerHandler_: Sends emails using a [`Swift_Mailer`](http://swiftmailer.org/) instance. +- _PushoverHandler_: Sends mobile notifications via the [Pushover](https://www.pushover.net/) API. +- _HipChatHandler_: Logs records to a [HipChat](http://hipchat.com) chat room using its API. +- _FlowdockHandler_: Logs records to a [Flowdock](https://www.flowdock.com/) account. +- _SlackHandler_: Logs records to a [Slack](https://www.slack.com/) account using the Slack API. +- _SlackbotHandler_: Logs records to a [Slack](https://www.slack.com/) account using the Slackbot incoming hook. +- _SlackWebhookHandler_: Logs records to a [Slack](https://www.slack.com/) account using Slack Webhooks. +- _MandrillHandler_: Sends emails via the Mandrill API using a [`Swift_Message`](http://swiftmailer.org/) instance. +- _FleepHookHandler_: Logs records to a [Fleep](https://fleep.io/) conversation using Webhooks. +- _IFTTTHandler_: Notifies an [IFTTT](https://ifttt.com/maker) trigger with the log channel, level name and message. + +### Log specific servers and networked logging + +- _SocketHandler_: Logs records to [sockets](http://php.net/fsockopen), use this + for UNIX and TCP sockets. See an [example](sockets.md). +- _AmqpHandler_: Logs records to an [amqp](http://www.amqp.org/) compatible + server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+). +- _GelfHandler_: Logs records to a [Graylog2](http://www.graylog2.org) server. +- _CubeHandler_: Logs records to a [Cube](http://square.github.com/cube/) server. +- _RavenHandler_: Logs records to a [Sentry](http://getsentry.com/) server using + [raven](https://packagist.org/packages/raven/raven). +- _ZendMonitorHandler_: Logs records to the Zend Monitor present in Zend Server. +- _NewRelicHandler_: Logs records to a [NewRelic](http://newrelic.com/) application. +- _LogglyHandler_: Logs records to a [Loggly](http://www.loggly.com/) account. +- _RollbarHandler_: Logs records to a [Rollbar](https://rollbar.com/) account. +- _SyslogUdpHandler_: Logs records to a remote [Syslogd](http://www.rsyslog.com/) server. +- _LogEntriesHandler_: Logs records to a [LogEntries](http://logentries.com/) account. + +### Logging in development + +- _FirePHPHandler_: Handler for [FirePHP](http://www.firephp.org/), providing + inline `console` messages within [FireBug](http://getfirebug.com/). +- _ChromePHPHandler_: Handler for [ChromePHP](http://www.chromephp.com/), providing + inline `console` messages within Chrome. +- _BrowserConsoleHandler_: Handler to send logs to browser's Javascript `console` with + no browser extension required. Most browsers supporting `console` API are supported. +- _PHPConsoleHandler_: Handler for [PHP Console](https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef), providing + inline `console` and notification popup messages within Chrome. + +### Log to databases + +- _RedisHandler_: Logs records to a [redis](http://redis.io) server. +- _MongoDBHandler_: Handler to write records in MongoDB via a + [Mongo](http://pecl.php.net/package/mongo) extension connection. +- _CouchDBHandler_: Logs records to a CouchDB server. +- _DoctrineCouchDBHandler_: Logs records to a CouchDB server via the Doctrine CouchDB ODM. +- _ElasticSearchHandler_: Logs records to an Elastic Search server. +- _DynamoDbHandler_: Logs records to a DynamoDB table with the [AWS SDK](https://github.com/aws/aws-sdk-php). + +### Wrappers / Special Handlers + +- _FingersCrossedHandler_: A very interesting wrapper. It takes a logger as + parameter and will accumulate log records of all levels until a record + exceeds the defined severity level. At which point it delivers all records, + including those of lower severity, to the handler it wraps. This means that + until an error actually happens you will not see anything in your logs, but + when it happens you will have the full information, including debug and info + records. This provides you with all the information you need, but only when + you need it. +- _DeduplicationHandler_: Useful if you are sending notifications or emails + when critical errors occur. It takes a logger as parameter and will + accumulate log records of all levels until the end of the request (or + `flush()` is called). At that point it delivers all records to the handler + it wraps, but only if the records are unique over a given time period + (60seconds by default). If the records are duplicates they are simply + discarded. The main use of this is in case of critical failure like if your + database is unreachable for example all your requests will fail and that + can result in a lot of notifications being sent. Adding this handler reduces + the amount of notifications to a manageable level. +- _WhatFailureGroupHandler_: This handler extends the _GroupHandler_ ignoring + exceptions raised by each child handler. This allows you to ignore issues + where a remote tcp connection may have died but you do not want your entire + application to crash and may wish to continue to log to other handlers. +- _BufferHandler_: This handler will buffer all the log records it receives + until `close()` is called at which point it will call `handleBatch()` on the + handler it wraps with all the log messages at once. This is very useful to + send an email with all records at once for example instead of having one mail + for every log record. +- _GroupHandler_: This handler groups other handlers. Every record received is + sent to all the handlers it is configured with. +- _FilterHandler_: This handler only lets records of the given levels through + to the wrapped handler. +- _SamplingHandler_: Wraps around another handler and lets you sample records + if you only want to store some of them. +- _NullHandler_: Any record it can handle will be thrown away. This can be used + to put on top of an existing handler stack to disable it temporarily. +- _PsrHandler_: Can be used to forward log records to an existing PSR-3 logger +- _TestHandler_: Used for testing, it records everything that is sent to it and + has accessors to read out the information. +- _HandlerWrapper_: A simple handler wrapper you can inherit from to create + your own wrappers easily. + +## Formatters + +- _LineFormatter_: Formats a log record into a one-line string. +- _HtmlFormatter_: Used to format log records into a human readable html table, mainly suitable for emails. +- _NormalizerFormatter_: Normalizes objects/resources down to strings so a record can easily be serialized/encoded. +- _ScalarFormatter_: Used to format log records into an associative array of scalar values. +- _JsonFormatter_: Encodes a log record into json. +- _WildfireFormatter_: Used to format log records into the Wildfire/FirePHP protocol, only useful for the FirePHPHandler. +- _ChromePHPFormatter_: Used to format log records into the ChromePHP format, only useful for the ChromePHPHandler. +- _GelfMessageFormatter_: Used to format log records into Gelf message instances, only useful for the GelfHandler. +- _LogstashFormatter_: Used to format log records into [logstash](http://logstash.net/) event json, useful for any handler listed under inputs [here](http://logstash.net/docs/latest). +- _ElasticaFormatter_: Used to format log records into an Elastica\Document object, only useful for the ElasticSearchHandler. +- _LogglyFormatter_: Used to format log records into Loggly messages, only useful for the LogglyHandler. +- _FlowdockFormatter_: Used to format log records into Flowdock messages, only useful for the FlowdockHandler. +- _MongoDBFormatter_: Converts \DateTime instances to \MongoDate and objects recursively to arrays, only useful with the MongoDBHandler. + +## Processors + +- _PsrLogMessageProcessor_: Processes a log record's message according to PSR-3 rules, replacing `{foo}` with the value from `$context['foo']`. +- _IntrospectionProcessor_: Adds the line/file/class/method from which the log call originated. +- _WebProcessor_: Adds the current request URI, request method and client IP to a log record. +- _MemoryUsageProcessor_: Adds the current memory usage to a log record. +- _MemoryPeakUsageProcessor_: Adds the peak memory usage to a log record. +- _ProcessIdProcessor_: Adds the process id to a log record. +- _UidProcessor_: Adds a unique identifier to a log record. +- _GitProcessor_: Adds the current git branch and commit to a log record. +- _TagProcessor_: Adds an array of predefined tags to a log record. + +## Third Party Packages + +Third party handlers, formatters and processors are +[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You +can also add your own there if you publish one. + +← [Usage](01-usage.md) | [Utility classes](03-utilities.md) → diff --git a/vendor/monolog/monolog/doc/03-utilities.md b/vendor/monolog/monolog/doc/03-utilities.md new file mode 100644 index 00000000..c62aa416 --- /dev/null +++ b/vendor/monolog/monolog/doc/03-utilities.md @@ -0,0 +1,13 @@ +# Utilities + +- _Registry_: The `Monolog\Registry` class lets you configure global loggers that you + can then statically access from anywhere. It is not really a best practice but can + help in some older codebases or for ease of use. +- _ErrorHandler_: The `Monolog\ErrorHandler` class allows you to easily register + a Logger instance as an exception handler, error handler or fatal error handler. +- _ErrorLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain log + level is reached. +- _ChannelLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain + log level is reached, depending on which channel received the log record. + +← [Handlers, Formatters and Processors](02-handlers-formatters-processors.md) | [Extending Monolog](04-extending.md) → diff --git a/vendor/monolog/monolog/doc/04-extending.md b/vendor/monolog/monolog/doc/04-extending.md new file mode 100644 index 00000000..ebd9104d --- /dev/null +++ b/vendor/monolog/monolog/doc/04-extending.md @@ -0,0 +1,76 @@ +# Extending Monolog + +Monolog is fully extensible, allowing you to adapt your logger to your needs. + +## Writing your own handler + +Monolog provides many built-in handlers. But if the one you need does not +exist, you can write it and use it in your logger. The only requirement is +to implement `Monolog\Handler\HandlerInterface`. + +Let's write a PDOHandler to log records to a database. We will extend the +abstract class provided by Monolog to keep things DRY. + +```php +pdo = $pdo; + parent::__construct($level, $bubble); + } + + protected function write(array $record) + { + if (!$this->initialized) { + $this->initialize(); + } + + $this->statement->execute(array( + 'channel' => $record['channel'], + 'level' => $record['level'], + 'message' => $record['formatted'], + 'time' => $record['datetime']->format('U'), + )); + } + + private function initialize() + { + $this->pdo->exec( + 'CREATE TABLE IF NOT EXISTS monolog ' + .'(channel VARCHAR(255), level INTEGER, message LONGTEXT, time INTEGER UNSIGNED)' + ); + $this->statement = $this->pdo->prepare( + 'INSERT INTO monolog (channel, level, message, time) VALUES (:channel, :level, :message, :time)' + ); + + $this->initialized = true; + } +} +``` + +You can now use this handler in your logger: + +```php +pushHandler(new PDOHandler(new PDO('sqlite:logs.sqlite'))); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); +``` + +The `Monolog\Handler\AbstractProcessingHandler` class provides most of the +logic needed for the handler, including the use of processors and the formatting +of the record (which is why we use ``$record['formatted']`` instead of ``$record['message']``). + +← [Utility classes](03-utilities.md) diff --git a/vendor/monolog/monolog/doc/sockets.md b/vendor/monolog/monolog/doc/sockets.md new file mode 100644 index 00000000..ea9cf0ea --- /dev/null +++ b/vendor/monolog/monolog/doc/sockets.md @@ -0,0 +1,39 @@ +Sockets Handler +=============== + +This handler allows you to write your logs to sockets using [fsockopen](http://php.net/fsockopen) +or [pfsockopen](http://php.net/pfsockopen). + +Persistent sockets are mainly useful in web environments where you gain some performance not closing/opening +the connections between requests. + +You can use a `unix://` prefix to access unix sockets and `udp://` to open UDP sockets instead of the default TCP. + +Basic Example +------------- + +```php +setPersistent(true); + +// Now add the handler +$logger->pushHandler($handler, Logger::DEBUG); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); + +``` + +In this example, using syslog-ng, you should see the log on the log server: + + cweb1 [2012-02-26 00:12:03] my_logger.INFO: My logger is now ready [] [] + diff --git a/vendor/monolog/monolog/phpunit.xml.dist b/vendor/monolog/monolog/phpunit.xml.dist new file mode 100644 index 00000000..20d82b63 --- /dev/null +++ b/vendor/monolog/monolog/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + + tests/Monolog/ + + + + + + src/Monolog/ + + + + + + + diff --git a/vendor/monolog/monolog/src/Monolog/ErrorHandler.php b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php new file mode 100644 index 00000000..7bfcd833 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/ErrorHandler.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 Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use Monolog\Handler\AbstractHandler; + +/** + * Monolog error handler + * + * A facility to enable logging of runtime errors, exceptions and fatal errors. + * + * Quick setup: ErrorHandler::register($logger); + * + * @author Jordi Boggiano + */ +class ErrorHandler +{ + private $logger; + + private $previousExceptionHandler; + private $uncaughtExceptionLevel; + + private $previousErrorHandler; + private $errorLevelMap; + private $handleOnlyReportedErrors; + + private $hasFatalErrorHandler; + private $fatalLevel; + private $reservedMemory; + private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR); + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Registers a new ErrorHandler for a given Logger + * + * By default it will handle errors, exceptions and fatal errors + * + * @param LoggerInterface $logger + * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling + * @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling + * @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling + * @return ErrorHandler + */ + public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null) + { + //Forces the autoloader to run for LogLevel. Fixes an autoload issue at compile-time on PHP5.3. See https://github.com/Seldaek/monolog/pull/929 + class_exists('\\Psr\\Log\\LogLevel', true); + + $handler = new static($logger); + if ($errorLevelMap !== false) { + $handler->registerErrorHandler($errorLevelMap); + } + if ($exceptionLevel !== false) { + $handler->registerExceptionHandler($exceptionLevel); + } + if ($fatalLevel !== false) { + $handler->registerFatalHandler($fatalLevel); + } + + return $handler; + } + + public function registerExceptionHandler($level = null, $callPrevious = true) + { + $prev = set_exception_handler(array($this, 'handleException')); + $this->uncaughtExceptionLevel = $level; + if ($callPrevious && $prev) { + $this->previousExceptionHandler = $prev; + } + } + + public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true) + { + $prev = set_error_handler(array($this, 'handleError'), $errorTypes); + $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap); + if ($callPrevious) { + $this->previousErrorHandler = $prev ?: true; + } + + $this->handleOnlyReportedErrors = $handleOnlyReportedErrors; + } + + public function registerFatalHandler($level = null, $reservedMemorySize = 20) + { + register_shutdown_function(array($this, 'handleFatalError')); + + $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); + $this->fatalLevel = $level; + $this->hasFatalErrorHandler = true; + } + + protected function defaultErrorLevelMap() + { + return array( + E_ERROR => LogLevel::CRITICAL, + E_WARNING => LogLevel::WARNING, + E_PARSE => LogLevel::ALERT, + E_NOTICE => LogLevel::NOTICE, + E_CORE_ERROR => LogLevel::CRITICAL, + E_CORE_WARNING => LogLevel::WARNING, + E_COMPILE_ERROR => LogLevel::ALERT, + E_COMPILE_WARNING => LogLevel::WARNING, + E_USER_ERROR => LogLevel::ERROR, + E_USER_WARNING => LogLevel::WARNING, + E_USER_NOTICE => LogLevel::NOTICE, + E_STRICT => LogLevel::NOTICE, + E_RECOVERABLE_ERROR => LogLevel::ERROR, + E_DEPRECATED => LogLevel::NOTICE, + E_USER_DEPRECATED => LogLevel::NOTICE, + ); + } + + /** + * @private + */ + public function handleException($e) + { + $this->logger->log( + $this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel, + sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()), + array('exception' => $e) + ); + + if ($this->previousExceptionHandler) { + call_user_func($this->previousExceptionHandler, $e); + } + + exit(255); + } + + /** + * @private + */ + public function handleError($code, $message, $file = '', $line = 0, $context = array()) + { + if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) { + return; + } + + // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries + if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) { + $level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL; + $this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line)); + } + + if ($this->previousErrorHandler === true) { + return false; + } elseif ($this->previousErrorHandler) { + return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context); + } + } + + /** + * @private + */ + public function handleFatalError() + { + $this->reservedMemory = null; + + $lastError = error_get_last(); + if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) { + $this->logger->log( + $this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel, + 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], + array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line']) + ); + + if ($this->logger instanceof Logger) { + foreach ($this->logger->getHandlers() as $handler) { + if ($handler instanceof AbstractHandler) { + $handler->close(); + } + } + } + } + } + + private static function codeToString($code) + { + switch ($code) { + case E_ERROR: + return 'E_ERROR'; + case E_WARNING: + return 'E_WARNING'; + case E_PARSE: + return 'E_PARSE'; + case E_NOTICE: + return 'E_NOTICE'; + case E_CORE_ERROR: + return 'E_CORE_ERROR'; + case E_CORE_WARNING: + return 'E_CORE_WARNING'; + case E_COMPILE_ERROR: + return 'E_COMPILE_ERROR'; + case E_COMPILE_WARNING: + return 'E_COMPILE_WARNING'; + case E_USER_ERROR: + return 'E_USER_ERROR'; + case E_USER_WARNING: + return 'E_USER_WARNING'; + case E_USER_NOTICE: + return 'E_USER_NOTICE'; + case E_STRICT: + return 'E_STRICT'; + case E_RECOVERABLE_ERROR: + return 'E_RECOVERABLE_ERROR'; + case E_DEPRECATED: + return 'E_DEPRECATED'; + case E_USER_DEPRECATED: + return 'E_USER_DEPRECATED'; + } + + return 'Unknown PHP error'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php new file mode 100644 index 00000000..9beda1e7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.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 Monolog\Formatter; + +use Monolog\Logger; + +/** + * Formats a log message according to the ChromePHP array format + * + * @author Christophe Coevoet + */ +class ChromePHPFormatter implements FormatterInterface +{ + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'log', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warn', + Logger::ERROR => 'error', + Logger::CRITICAL => 'error', + Logger::ALERT => 'error', + Logger::EMERGENCY => 'error', + ); + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $backtrace = 'unknown'; + if (isset($record['extra']['file'], $record['extra']['line'])) { + $backtrace = $record['extra']['file'].' : '.$record['extra']['line']; + unset($record['extra']['file'], $record['extra']['line']); + } + + $message = array('message' => $record['message']); + if ($record['context']) { + $message['context'] = $record['context']; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + } + if (count($message) === 1) { + $message = reset($message); + } + + return array( + $record['channel'], + $message, + $backtrace, + $this->logLevels[$record['level']], + ); + } + + public function formatBatch(array $records) + { + $formatted = array(); + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php new file mode 100644 index 00000000..4c556cf1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.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 Monolog\Formatter; + +use Elastica\Document; + +/** + * Format a log message into an Elastica Document + * + * @author Jelle Vink + */ +class ElasticaFormatter extends NormalizerFormatter +{ + /** + * @var string Elastic search index name + */ + protected $index; + + /** + * @var string Elastic search document type + */ + protected $type; + + /** + * @param string $index Elastic Search index name + * @param string $type Elastic Search document type + */ + public function __construct($index, $type) + { + // elasticsearch requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->index = $index; + $this->type = $type; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + return $this->getDocument($record); + } + + /** + * Getter index + * @return string + */ + public function getIndex() + { + return $this->index; + } + + /** + * Getter type + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Convert a log message into an Elastica Document + * + * @param array $record Log message + * @return Document + */ + protected function getDocument($record) + { + $document = new Document(); + $document->setData($record); + $document->setType($this->type); + $document->setIndex($this->index); + + return $document; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php new file mode 100644 index 00000000..5094af3c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * formats the record to be used in the FlowdockHandler + * + * @author Dominik Liebler + */ +class FlowdockFormatter implements FormatterInterface +{ + /** + * @var string + */ + private $source; + + /** + * @var string + */ + private $sourceEmail; + + /** + * @param string $source + * @param string $sourceEmail + */ + public function __construct($source, $sourceEmail) + { + $this->source = $source; + $this->sourceEmail = $sourceEmail; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $tags = array( + '#logs', + '#' . strtolower($record['level_name']), + '#' . $record['channel'], + ); + + foreach ($record['extra'] as $value) { + $tags[] = '#' . $value; + } + + $subject = sprintf( + 'in %s: %s - %s', + $this->source, + $record['level_name'], + $this->getShortMessage($record['message']) + ); + + $record['flowdock'] = array( + 'source' => $this->source, + 'from_address' => $this->sourceEmail, + 'subject' => $subject, + 'content' => $record['message'], + 'tags' => $tags, + 'project' => $this->source, + ); + + return $record; + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + $formatted = array(); + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } + + /** + * @param string $message + * + * @return string + */ + public function getShortMessage($message) + { + static $hasMbString; + + if (null === $hasMbString) { + $hasMbString = function_exists('mb_strlen'); + } + + $maxLength = 45; + + if ($hasMbString) { + if (mb_strlen($message, 'UTF-8') > $maxLength) { + $message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...'; + } + } else { + if (strlen($message) > $maxLength) { + $message = substr($message, 0, $maxLength - 4) . ' ...'; + } + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php new file mode 100644 index 00000000..02632bb5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.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 Monolog\Formatter; + +/** + * Class FluentdFormatter + * + * Serializes a log message to Fluentd unix socket protocol + * + * Fluentd config: + * + * + * type unix + * path /var/run/td-agent/td-agent.sock + * + * + * Monolog setup: + * + * $logger = new Monolog\Logger('fluent.tag'); + * $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock'); + * $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter()); + * $logger->pushHandler($fluentHandler); + * + * @author Andrius Putna + */ +class FluentdFormatter implements FormatterInterface +{ + /** + * @var bool $levelTag should message level be a part of the fluentd tag + */ + protected $levelTag = false; + + public function __construct($levelTag = false) + { + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s FluentdUnixFormatter'); + } + + $this->levelTag = (bool) $levelTag; + } + + public function isUsingLevelsInTag() + { + return $this->levelTag; + } + + public function format(array $record) + { + $tag = $record['channel']; + if ($this->levelTag) { + $tag .= '.' . strtolower($record['level_name']); + } + + $message = array( + 'message' => $record['message'], + 'extra' => $record['extra'], + ); + + if (!$this->levelTag) { + $message['level'] = $record['level']; + $message['level_name'] = $record['level_name']; + } + + return json_encode(array($tag, $record['datetime']->getTimestamp(), $message)); + } + + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php new file mode 100644 index 00000000..b5de7511 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.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 Monolog\Formatter; + +/** + * Interface for formatters + * + * @author Jordi Boggiano + */ +interface FormatterInterface +{ + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + */ + public function format(array $record); + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records); +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php new file mode 100644 index 00000000..64e76652 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.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 Monolog\Formatter; + +use Monolog\Logger; +use Gelf\Message; + +/** + * Serializes a log message to GELF + * @see http://www.graylog2.org/about/gelf + * + * @author Matt Lehner + */ +class GelfMessageFormatter extends NormalizerFormatter +{ + const MAX_LENGTH = 32766; + + /** + * @var string the name of the system for the Gelf log message + */ + protected $systemName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * Translates Monolog log levels to Graylog2 log priorities. + */ + private $logLevels = array( + Logger::DEBUG => 7, + Logger::INFO => 6, + Logger::NOTICE => 5, + Logger::WARNING => 4, + Logger::ERROR => 3, + Logger::CRITICAL => 2, + Logger::ALERT => 1, + Logger::EMERGENCY => 0, + ); + + public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_') + { + parent::__construct('U.u'); + + $this->systemName = $systemName ?: gethostname(); + + $this->extraPrefix = $extraPrefix; + $this->contextPrefix = $contextPrefix; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + if (!isset($record['datetime'], $record['message'], $record['level'])) { + throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given'); + } + + $message = new Message(); + $message + ->setTimestamp($record['datetime']) + ->setShortMessage((string) $record['message']) + ->setHost($this->systemName) + ->setLevel($this->logLevels[$record['level']]); + + // start count with message length + system name length + 200 for padding / metadata + $len = 200 + strlen((string) $record['message']) + strlen($this->systemName); + + if ($len > self::MAX_LENGTH) { + $message->setShortMessage(substr($record['message'], 0, self::MAX_LENGTH - 200)); + + return $message; + } + + if (isset($record['channel'])) { + $message->setFacility($record['channel']); + $len += strlen($record['channel']); + } + if (isset($record['extra']['line'])) { + $message->setLine($record['extra']['line']); + $len += 10; + unset($record['extra']['line']); + } + if (isset($record['extra']['file'])) { + $message->setFile($record['extra']['file']); + $len += strlen($record['extra']['file']); + unset($record['extra']['file']); + } + + foreach ($record['extra'] as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len += strlen($this->extraPrefix . $key . $val); + if ($len > self::MAX_LENGTH) { + $message->setAdditional($this->extraPrefix . $key, substr($val, 0, self::MAX_LENGTH - $len)); + break; + } + $message->setAdditional($this->extraPrefix . $key, $val); + } + + foreach ($record['context'] as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len += strlen($this->contextPrefix . $key . $val); + if ($len > self::MAX_LENGTH) { + $message->setAdditional($this->contextPrefix . $key, substr($val, 0, self::MAX_LENGTH - $len)); + break; + } + $message->setAdditional($this->contextPrefix . $key, $val); + } + + if (null === $message->getFile() && isset($record['context']['exception']['file'])) { + if (preg_match("/^(.+):([0-9]+)$/", $record['context']['exception']['file'], $matches)) { + $message->setFile($matches[1]); + $message->setLine($matches[2]); + } + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php new file mode 100644 index 00000000..3eec95f6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.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 Monolog\Formatter; + +use Monolog\Logger; + +/** + * Formats incoming records into an HTML table + * + * This is especially useful for html email logging + * + * @author Tiago Brito + */ +class HtmlFormatter extends NormalizerFormatter +{ + /** + * Translates Monolog log levels to html color priorities. + */ + protected $logLevels = array( + Logger::DEBUG => '#cccccc', + Logger::INFO => '#468847', + Logger::NOTICE => '#3a87ad', + Logger::WARNING => '#c09853', + Logger::ERROR => '#f0ad4e', + Logger::CRITICAL => '#FF7708', + Logger::ALERT => '#C12A19', + Logger::EMERGENCY => '#000000', + ); + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($dateFormat = null) + { + parent::__construct($dateFormat); + } + + /** + * Creates an HTML table row + * + * @param string $th Row header content + * @param string $td Row standard cell content + * @param bool $escapeTd false if td content must not be html escaped + * @return string + */ + protected function addRow($th, $td = ' ', $escapeTd = true) + { + $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8'); + if ($escapeTd) { + $td = '
'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'
'; + } + + return "\n$th:\n".$td."\n"; + } + + /** + * Create a HTML h1 tag + * + * @param string $title Text to be in the h1 + * @param int $level Error level + * @return string + */ + protected function addTitle($title, $level) + { + $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8'); + + return '

'.$title.'

'; + } + + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + */ + public function format(array $record) + { + $output = $this->addTitle($record['level_name'], $record['level']); + $output .= ''; + + $output .= $this->addRow('Message', (string) $record['message']); + $output .= $this->addRow('Time', $record['datetime']->format($this->dateFormat)); + $output .= $this->addRow('Channel', $record['channel']); + if ($record['context']) { + $embeddedTable = '
'; + foreach ($record['context'] as $key => $value) { + $embeddedTable .= $this->addRow($key, $this->convertToString($value)); + } + $embeddedTable .= '
'; + $output .= $this->addRow('Context', $embeddedTable, false); + } + if ($record['extra']) { + $embeddedTable = ''; + foreach ($record['extra'] as $key => $value) { + $embeddedTable .= $this->addRow($key, $this->convertToString($value)); + } + $embeddedTable .= '
'; + $output .= $this->addRow('Extra', $embeddedTable, false); + } + + return $output.''; + } + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + protected function convertToString($data) + { + if (null === $data || is_scalar($data)) { + return (string) $data; + } + + $data = $this->normalize($data); + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return str_replace('\\/', '/', json_encode($data)); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php new file mode 100644 index 00000000..4b2be77d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Exception; +use Throwable; + +/** + * Encodes whatever record data is passed to it as json + * + * This can be useful to log to databases or remote APIs + * + * @author Jordi Boggiano + */ +class JsonFormatter extends NormalizerFormatter +{ + const BATCH_MODE_JSON = 1; + const BATCH_MODE_NEWLINES = 2; + + protected $batchMode; + protected $appendNewline; + /** + * @var bool + */ + protected $includeStacktraces = false; + + /** + * @param int $batchMode + */ + public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = true) + { + $this->batchMode = $batchMode; + $this->appendNewline = $appendNewline; + } + + /** + * The batch mode option configures the formatting style for + * multiple records. By default, multiple records will be + * formatted as a JSON-encoded array. However, for + * compatibility with some API endpoints, alternative styles + * are available. + * + * @return int + */ + public function getBatchMode() + { + return $this->batchMode; + } + + /** + * True if newlines are appended to every formatted record + * + * @return bool + */ + public function isAppendingNewlines() + { + return $this->appendNewline; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : ''); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + switch ($this->batchMode) { + case static::BATCH_MODE_NEWLINES: + return $this->formatBatchNewlines($records); + + case static::BATCH_MODE_JSON: + default: + return $this->formatBatchJson($records); + } + } + + /** + * @param bool $include + */ + public function includeStacktraces($include = true) + { + $this->includeStacktraces = $include; + } + + /** + * Return a JSON-encoded array of records. + * + * @param array $records + * @return string + */ + protected function formatBatchJson(array $records) + { + return $this->toJson($this->normalize($records), true); + } + + /** + * Use new lines to separate records instead of a + * JSON-encoded array. + * + * @param array $records + * @return string + */ + protected function formatBatchNewlines(array $records) + { + $instance = $this; + + $oldNewline = $this->appendNewline; + $this->appendNewline = false; + array_walk($records, function (&$value, $key) use ($instance) { + $value = $instance->format($value); + }); + $this->appendNewline = $oldNewline; + + return implode("\n", $records); + } + + /** + * Normalizes given $data. + * + * @param mixed $data + * + * @return mixed + */ + protected function normalize($data) + { + if (is_array($data) || $data instanceof \Traversable) { + $normalized = array(); + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ >= 1000) { + $normalized['...'] = 'Over 1000 items, aborting normalization'; + break; + } + $normalized[$key] = $this->normalize($value); + } + + return $normalized; + } + + if ($data instanceof Exception || $data instanceof Throwable) { + return $this->normalizeException($data); + } + + return $data; + } + + /** + * Normalizes given exception with or without its own stack trace based on + * `includeStacktraces` property. + * + * @param Exception|Throwable $e + * + * @return array + */ + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof Exception && !$e instanceof Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); + } + + $data = array( + 'class' => get_class($e), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile().':'.$e->getLine(), + ); + + if ($this->includeStacktraces) { + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } elseif (isset($frame['function']) && $frame['function'] === '{closure}') { + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $frame['function']; + } else { + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $this->normalize($frame); + } + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous); + } + + return $data; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php new file mode 100644 index 00000000..d3e209e6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php @@ -0,0 +1,179 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Formats incoming records into a one-line string + * + * This is especially useful for logging to files + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +class LineFormatter extends NormalizerFormatter +{ + const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; + + protected $format; + protected $allowInlineLineBreaks; + protected $ignoreEmptyContextAndExtra; + protected $includeStacktraces; + + /** + * @param string $format The format of the message + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries + * @param bool $ignoreEmptyContextAndExtra + */ + public function __construct($format = null, $dateFormat = null, $allowInlineLineBreaks = false, $ignoreEmptyContextAndExtra = false) + { + $this->format = $format ?: static::SIMPLE_FORMAT; + $this->allowInlineLineBreaks = $allowInlineLineBreaks; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + parent::__construct($dateFormat); + } + + public function includeStacktraces($include = true) + { + $this->includeStacktraces = $include; + if ($this->includeStacktraces) { + $this->allowInlineLineBreaks = true; + } + } + + public function allowInlineLineBreaks($allow = true) + { + $this->allowInlineLineBreaks = $allow; + } + + public function ignoreEmptyContextAndExtra($ignore = true) + { + $this->ignoreEmptyContextAndExtra = $ignore; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $vars = parent::format($record); + + $output = $this->format; + + foreach ($vars['extra'] as $var => $val) { + if (false !== strpos($output, '%extra.'.$var.'%')) { + $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output); + unset($vars['extra'][$var]); + } + } + + + foreach ($vars['context'] as $var => $val) { + if (false !== strpos($output, '%context.'.$var.'%')) { + $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output); + unset($vars['context'][$var]); + } + } + + if ($this->ignoreEmptyContextAndExtra) { + if (empty($vars['context'])) { + unset($vars['context']); + $output = str_replace('%context%', '', $output); + } + + if (empty($vars['extra'])) { + unset($vars['extra']); + $output = str_replace('%extra%', '', $output); + } + } + + foreach ($vars as $var => $val) { + if (false !== strpos($output, '%'.$var.'%')) { + $output = str_replace('%'.$var.'%', $this->stringify($val), $output); + } + } + + // remove leftover %extra.xxx% and %context.xxx% if any + if (false !== strpos($output, '%')) { + $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output); + } + + return $output; + } + + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + public function stringify($value) + { + return $this->replaceNewlines($this->convertToString($value)); + } + + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof \Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); + } + + $previousText = ''; + if ($previous = $e->getPrevious()) { + do { + $previousText .= ', '.get_class($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine(); + } while ($previous = $previous->getPrevious()); + } + + $str = '[object] ('.get_class($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')'; + if ($this->includeStacktraces) { + $str .= "\n[stacktrace]\n".$e->getTraceAsString()."\n"; + } + + return $str; + } + + protected function convertToString($data) + { + if (null === $data || is_bool($data)) { + return var_export($data, true); + } + + if (is_scalar($data)) { + return (string) $data; + } + + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return $this->toJson($data, true); + } + + return str_replace('\\/', '/', @json_encode($data)); + } + + protected function replaceNewlines($str) + { + if ($this->allowInlineLineBreaks) { + if (0 === strpos($str, '{')) { + return str_replace(array('\r', '\n'), array("\r", "\n"), $str); + } + + return $str; + } + + return str_replace(array("\r\n", "\r", "\n"), ' ', $str); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php new file mode 100644 index 00000000..401859bb --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.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 Monolog\Formatter; + +/** + * Encodes message information into JSON in a format compatible with Loggly. + * + * @author Adam Pancutt + */ +class LogglyFormatter extends JsonFormatter +{ + /** + * Overrides the default batch mode to new lines for compatibility with the + * Loggly bulk API. + * + * @param int $batchMode + */ + public function __construct($batchMode = self::BATCH_MODE_NEWLINES, $appendNewline = false) + { + parent::__construct($batchMode, $appendNewline); + } + + /** + * Appends the 'timestamp' parameter for indexing by Loggly. + * + * @see https://www.loggly.com/docs/automated-parsing/#json + * @see \Monolog\Formatter\JsonFormatter::format() + */ + public function format(array $record) + { + if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTime)) { + $record["timestamp"] = $record["datetime"]->format("Y-m-d\TH:i:s.uO"); + // TODO 2.0 unset the 'datetime' parameter, retained for BC + } + + return parent::format($record); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php new file mode 100644 index 00000000..8f83bec0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.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 Monolog\Formatter; + +/** + * Serializes a log message to Logstash Event Format + * + * @see http://logstash.net/ + * @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb + * + * @author Tim Mower + */ +class LogstashFormatter extends NormalizerFormatter +{ + const V0 = 0; + const V1 = 1; + + /** + * @var string the name of the system for the Logstash log message, used to fill the @source field + */ + protected $systemName; + + /** + * @var string an application name for the Logstash log message, used to fill the @type field + */ + protected $applicationName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * @var int logstash format version to use + */ + protected $version; + + /** + * @param string $applicationName the application that sends the data, used as the "type" field of logstash + * @param string $systemName the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine + * @param string $extraPrefix prefix for extra keys inside logstash "fields" + * @param string $contextPrefix prefix for context keys inside logstash "fields", defaults to ctxt_ + * @param int $version the logstash format version to use, defaults to 0 + */ + public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $version = self::V0) + { + // logstash requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->systemName = $systemName ?: gethostname(); + $this->applicationName = $applicationName; + $this->extraPrefix = $extraPrefix; + $this->contextPrefix = $contextPrefix; + $this->version = $version; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + if ($this->version === self::V1) { + $message = $this->formatV1($record); + } else { + $message = $this->formatV0($record); + } + + return $this->toJson($message) . "\n"; + } + + protected function formatV0(array $record) + { + if (empty($record['datetime'])) { + $record['datetime'] = gmdate('c'); + } + $message = array( + '@timestamp' => $record['datetime'], + '@source' => $this->systemName, + '@fields' => array(), + ); + if (isset($record['message'])) { + $message['@message'] = $record['message']; + } + if (isset($record['channel'])) { + $message['@tags'] = array($record['channel']); + $message['@fields']['channel'] = $record['channel']; + } + if (isset($record['level'])) { + $message['@fields']['level'] = $record['level']; + } + if ($this->applicationName) { + $message['@type'] = $this->applicationName; + } + if (isset($record['extra']['server'])) { + $message['@source_host'] = $record['extra']['server']; + } + if (isset($record['extra']['url'])) { + $message['@source_path'] = $record['extra']['url']; + } + if (!empty($record['extra'])) { + foreach ($record['extra'] as $key => $val) { + $message['@fields'][$this->extraPrefix . $key] = $val; + } + } + if (!empty($record['context'])) { + foreach ($record['context'] as $key => $val) { + $message['@fields'][$this->contextPrefix . $key] = $val; + } + } + + return $message; + } + + protected function formatV1(array $record) + { + if (empty($record['datetime'])) { + $record['datetime'] = gmdate('c'); + } + $message = array( + '@timestamp' => $record['datetime'], + '@version' => 1, + 'host' => $this->systemName, + ); + if (isset($record['message'])) { + $message['message'] = $record['message']; + } + if (isset($record['channel'])) { + $message['type'] = $record['channel']; + $message['channel'] = $record['channel']; + } + if (isset($record['level_name'])) { + $message['level'] = $record['level_name']; + } + if ($this->applicationName) { + $message['type'] = $this->applicationName; + } + if (!empty($record['extra'])) { + foreach ($record['extra'] as $key => $val) { + $message[$this->extraPrefix . $key] = $val; + } + } + if (!empty($record['context'])) { + foreach ($record['context'] as $key => $val) { + $message[$this->contextPrefix . $key] = $val; + } + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php new file mode 100644 index 00000000..eb067bb7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.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 Monolog\Formatter; + +/** + * Formats a record for use with the MongoDBHandler. + * + * @author Florian Plattner + */ +class MongoDBFormatter implements FormatterInterface +{ + private $exceptionTraceAsString; + private $maxNestingLevel; + + /** + * @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record['context'] is 2 + * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings + */ + public function __construct($maxNestingLevel = 3, $exceptionTraceAsString = true) + { + $this->maxNestingLevel = max($maxNestingLevel, 0); + $this->exceptionTraceAsString = (bool) $exceptionTraceAsString; + } + + /** + * {@inheritDoc} + */ + public function format(array $record) + { + return $this->formatArray($record); + } + + /** + * {@inheritDoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + protected function formatArray(array $record, $nestingLevel = 0) + { + if ($this->maxNestingLevel == 0 || $nestingLevel <= $this->maxNestingLevel) { + foreach ($record as $name => $value) { + if ($value instanceof \DateTime) { + $record[$name] = $this->formatDate($value, $nestingLevel + 1); + } elseif ($value instanceof \Exception) { + $record[$name] = $this->formatException($value, $nestingLevel + 1); + } elseif (is_array($value)) { + $record[$name] = $this->formatArray($value, $nestingLevel + 1); + } elseif (is_object($value)) { + $record[$name] = $this->formatObject($value, $nestingLevel + 1); + } + } + } else { + $record = '[...]'; + } + + return $record; + } + + protected function formatObject($value, $nestingLevel) + { + $objectVars = get_object_vars($value); + $objectVars['class'] = get_class($value); + + return $this->formatArray($objectVars, $nestingLevel); + } + + protected function formatException(\Exception $exception, $nestingLevel) + { + $formattedException = array( + 'class' => get_class($exception), + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + ); + + if ($this->exceptionTraceAsString === true) { + $formattedException['trace'] = $exception->getTraceAsString(); + } else { + $formattedException['trace'] = $exception->getTrace(); + } + + return $this->formatArray($formattedException, $nestingLevel); + } + + protected function formatDate(\DateTime $value, $nestingLevel) + { + return new \MongoDate($value->getTimestamp()); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php new file mode 100644 index 00000000..d4414882 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php @@ -0,0 +1,297 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Exception; + +/** + * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets + * + * @author Jordi Boggiano + */ +class NormalizerFormatter implements FormatterInterface +{ + const SIMPLE_DATE = "Y-m-d H:i:s"; + + protected $dateFormat; + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($dateFormat = null) + { + $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE; + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter'); + } + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return $this->normalize($record); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + protected function normalize($data) + { + if (null === $data || is_scalar($data)) { + if (is_float($data)) { + if (is_infinite($data)) { + return ($data > 0 ? '' : '-') . 'INF'; + } + if (is_nan($data)) { + return 'NaN'; + } + } + + return $data; + } + + if (is_array($data)) { + $normalized = array(); + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ >= 1000) { + $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization'; + break; + } + $normalized[$key] = $this->normalize($value); + } + + return $normalized; + } + + if ($data instanceof \DateTime) { + return $data->format($this->dateFormat); + } + + if (is_object($data)) { + // TODO 2.0 only check for Throwable + if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) { + return $this->normalizeException($data); + } + + // non-serializable objects that implement __toString stringified + if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) { + $value = $data->__toString(); + } else { + // the rest is json-serialized in some way + $value = $this->toJson($data, true); + } + + return sprintf("[object] (%s: %s)", get_class($data), $value); + } + + if (is_resource($data)) { + return sprintf('[resource] (%s)', get_resource_type($data)); + } + + return '[unknown('.gettype($data).')]'; + } + + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); + } + + $data = array( + 'class' => get_class($e), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile().':'.$e->getLine(), + ); + + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $data['faultcode'] = $e->faultcode; + } + + if (isset($e->faultactor)) { + $data['faultactor'] = $e->faultactor; + } + + if (isset($e->detail)) { + $data['detail'] = $e->detail; + } + } + + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } elseif (isset($frame['function']) && $frame['function'] === '{closure}') { + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $frame['function']; + } else { + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $this->toJson($this->normalize($frame), true); + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous); + } + + return $data; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @param bool $ignoreErrors + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string + */ + protected function toJson($data, $ignoreErrors = false) + { + // suppress json_encode errors since it's twitchy with some inputs + if ($ignoreErrors) { + return @$this->jsonEncode($data); + } + + $json = $this->jsonEncode($data); + + if ($json === false) { + $json = $this->handleJsonError(json_last_error(), $data); + } + + return $json; + } + + /** + * @param mixed $data + * @return string JSON encoded data or null on failure + */ + private function jsonEncode($data) + { + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return json_encode($data); + } + + /** + * Handle a json_encode failure. + * + * If the failure is due to invalid string encoding, try to clean the + * input and encode again. If the second encoding attempt fails, the + * inital error is not encoding related or the input can't be cleaned then + * raise a descriptive exception. + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @throws \RuntimeException if failure can't be corrected + * @return string JSON encoded data after error correction + */ + private function handleJsonError($code, $data) + { + if ($code !== JSON_ERROR_UTF8) { + $this->throwEncodeError($code, $data); + } + + if (is_string($data)) { + $this->detectAndCleanUtf8($data); + } elseif (is_array($data)) { + array_walk_recursive($data, array($this, 'detectAndCleanUtf8')); + } else { + $this->throwEncodeError($code, $data); + } + + $json = $this->jsonEncode($data); + + if ($json === false) { + $this->throwEncodeError(json_last_error(), $data); + } + + return $json; + } + + /** + * Throws an exception according to a given code with a customized message + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @throws \RuntimeException + */ + private function throwEncodeError($code, $data) + { + switch ($code) { + case JSON_ERROR_DEPTH: + $msg = 'Maximum stack depth exceeded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $msg = 'Underflow or the modes mismatch'; + break; + case JSON_ERROR_CTRL_CHAR: + $msg = 'Unexpected control character found'; + break; + case JSON_ERROR_UTF8: + $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + default: + $msg = 'Unknown error'; + } + + throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true)); + } + + /** + * Detect invalid UTF-8 string characters and convert to valid UTF-8. + * + * Valid UTF-8 input will be left unmodified, but strings containing + * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed + * original encoding of ISO-8859-15. This conversion may result in + * incorrect output if the actual encoding was not ISO-8859-15, but it + * will be clean UTF-8 output and will not rely on expensive and fragile + * detection algorithms. + * + * Function converts the input in place in the passed variable so that it + * can be used as a callback for array_walk_recursive. + * + * @param mixed &$data Input to check and convert if needed + * @private + */ + public function detectAndCleanUtf8(&$data) + { + if (is_string($data) && !preg_match('//u', $data)) { + $data = preg_replace_callback( + '/[\x80-\xFF]+/', + function ($m) { return utf8_encode($m[0]); }, + $data + ); + $data = str_replace( + array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'), + array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'), + $data + ); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php new file mode 100644 index 00000000..5d345d53 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.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 Monolog\Formatter; + +/** + * Formats data into an associative array of scalar values. + * Objects and arrays will be JSON encoded. + * + * @author Andrew Lawson + */ +class ScalarFormatter extends NormalizerFormatter +{ + /** + * {@inheritdoc} + */ + public function format(array $record) + { + foreach ($record as $key => $value) { + $record[$key] = $this->normalizeValue($value); + } + + return $record; + } + + /** + * @param mixed $value + * @return mixed + */ + protected function normalizeValue($value) + { + $normalized = $this->normalize($value); + + if (is_array($normalized) || is_object($normalized)) { + return $this->toJson($normalized, true); + } + + return $normalized; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php new file mode 100644 index 00000000..654710a8 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Serializes a log message according to Wildfire's header requirements + * + * @author Eric Clemmons (@ericclemmons) + * @author Christophe Coevoet + * @author Kirill chEbba Chebunin + */ +class WildfireFormatter extends NormalizerFormatter +{ + const TABLE = 'table'; + + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'LOG', + Logger::INFO => 'INFO', + Logger::NOTICE => 'INFO', + Logger::WARNING => 'WARN', + Logger::ERROR => 'ERROR', + Logger::CRITICAL => 'ERROR', + Logger::ALERT => 'ERROR', + Logger::EMERGENCY => 'ERROR', + ); + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $file = $line = ''; + if (isset($record['extra']['file'])) { + $file = $record['extra']['file']; + unset($record['extra']['file']); + } + if (isset($record['extra']['line'])) { + $line = $record['extra']['line']; + unset($record['extra']['line']); + } + + $record = $this->normalize($record); + $message = array('message' => $record['message']); + $handleError = false; + if ($record['context']) { + $message['context'] = $record['context']; + $handleError = true; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + $handleError = true; + } + if (count($message) === 1) { + $message = reset($message); + } + + if (isset($record['context'][self::TABLE])) { + $type = 'TABLE'; + $label = $record['channel'] .': '. $record['message']; + $message = $record['context'][self::TABLE]; + } else { + $type = $this->logLevels[$record['level']]; + $label = $record['channel']; + } + + // Create JSON object describing the appearance of the message in the console + $json = $this->toJson(array( + array( + 'Type' => $type, + 'File' => $file, + 'Line' => $line, + 'Label' => $label, + ), + $message, + ), $handleError); + + // The message itself is a serialization of the above JSON object + it's length + return sprintf( + '%s|%s|', + strlen($json), + $json + ); + } + + public function formatBatch(array $records) + { + throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); + } + + protected function normalize($data) + { + if (is_object($data) && !$data instanceof \DateTime) { + return $data; + } + + return parent::normalize($data); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php new file mode 100644 index 00000000..758a425c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Base Handler class providing the Handler structure + * + * @author Jordi Boggiano + */ +abstract class AbstractHandler implements HandlerInterface +{ + protected $level = Logger::DEBUG; + protected $bubble = true; + + /** + * @var FormatterInterface + */ + protected $formatter; + protected $processors = array(); + + /** + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + $this->setLevel($level); + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $record['level'] >= $this->level; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * Closes the handler. + * + * This will be called automatically when the object is destroyed + */ + public function close() + { + } + + /** + * {@inheritdoc} + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Sets minimum logging level at which this handler will be triggered. + * + * @param int|string $level Level or level name + * @return self + */ + public function setLevel($level) + { + $this->level = Logger::toMonologLevel($level); + + return $this; + } + + /** + * Gets minimum logging level at which this handler will be triggered. + * + * @return int + */ + public function getLevel() + { + return $this->level; + } + + /** + * Sets the bubbling behavior. + * + * @param Boolean $bubble true means that this handler allows bubbling. + * false means that bubbling is not permitted. + * @return self + */ + public function setBubble($bubble) + { + $this->bubble = $bubble; + + return $this; + } + + /** + * Gets the bubbling behavior. + * + * @return Boolean true means that this handler allows bubbling. + * false means that bubbling is not permitted. + */ + public function getBubble() + { + return $this->bubble; + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Exception $e) { + // do nothing + } catch (\Throwable $e) { + // do nothing + } + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php new file mode 100644 index 00000000..6f18f72e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.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 Monolog\Handler; + +/** + * Base Handler class providing the Handler structure + * + * Classes extending it should (in most cases) only implement write($record) + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +abstract class AbstractProcessingHandler extends AbstractHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + $record = $this->processRecord($record); + + $record['formatted'] = $this->getFormatter()->format($record); + + $this->write($record); + + return false === $this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * @return void + */ + abstract protected function write(array $record); + + /** + * Processes a record. + * + * @param array $record + * @return array + */ + protected function processRecord(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php new file mode 100644 index 00000000..e2b2832d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * Common syslog functionality + */ +abstract class AbstractSyslogHandler extends AbstractProcessingHandler +{ + protected $facility; + + /** + * Translates Monolog log levels to syslog log priorities. + */ + protected $logLevels = array( + Logger::DEBUG => LOG_DEBUG, + Logger::INFO => LOG_INFO, + Logger::NOTICE => LOG_NOTICE, + Logger::WARNING => LOG_WARNING, + Logger::ERROR => LOG_ERR, + Logger::CRITICAL => LOG_CRIT, + Logger::ALERT => LOG_ALERT, + Logger::EMERGENCY => LOG_EMERG, + ); + + /** + * List of valid log facility names. + */ + protected $facilities = array( + 'auth' => LOG_AUTH, + 'authpriv' => LOG_AUTHPRIV, + 'cron' => LOG_CRON, + 'daemon' => LOG_DAEMON, + 'kern' => LOG_KERN, + 'lpr' => LOG_LPR, + 'mail' => LOG_MAIL, + 'news' => LOG_NEWS, + 'syslog' => LOG_SYSLOG, + 'user' => LOG_USER, + 'uucp' => LOG_UUCP, + ); + + /** + * @param mixed $facility + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($facility = LOG_USER, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->facilities['local0'] = LOG_LOCAL0; + $this->facilities['local1'] = LOG_LOCAL1; + $this->facilities['local2'] = LOG_LOCAL2; + $this->facilities['local3'] = LOG_LOCAL3; + $this->facilities['local4'] = LOG_LOCAL4; + $this->facilities['local5'] = LOG_LOCAL5; + $this->facilities['local6'] = LOG_LOCAL6; + $this->facilities['local7'] = LOG_LOCAL7; + } else { + $this->facilities['local0'] = 128; // LOG_LOCAL0 + $this->facilities['local1'] = 136; // LOG_LOCAL1 + $this->facilities['local2'] = 144; // LOG_LOCAL2 + $this->facilities['local3'] = 152; // LOG_LOCAL3 + $this->facilities['local4'] = 160; // LOG_LOCAL4 + $this->facilities['local5'] = 168; // LOG_LOCAL5 + $this->facilities['local6'] = 176; // LOG_LOCAL6 + $this->facilities['local7'] = 184; // LOG_LOCAL7 + } + + // convert textual description of facility to syslog constant + if (array_key_exists(strtolower($facility), $this->facilities)) { + $facility = $this->facilities[strtolower($facility)]; + } elseif (!in_array($facility, array_values($this->facilities), true)) { + throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); + } + + $this->facility = $facility; + } + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php new file mode 100644 index 00000000..e5a46bc0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.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 Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\JsonFormatter; +use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Channel\AMQPChannel; +use AMQPExchange; + +class AmqpHandler extends AbstractProcessingHandler +{ + /** + * @var AMQPExchange|AMQPChannel $exchange + */ + protected $exchange; + + /** + * @var string + */ + protected $exchangeName; + + /** + * @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use + * @param string $exchangeName + * @param int $level + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($exchange, $exchangeName = 'log', $level = Logger::DEBUG, $bubble = true) + { + if ($exchange instanceof AMQPExchange) { + $exchange->setName($exchangeName); + } elseif ($exchange instanceof AMQPChannel) { + $this->exchangeName = $exchangeName; + } else { + throw new \InvalidArgumentException('PhpAmqpLib\Channel\AMQPChannel or AMQPExchange instance required'); + } + $this->exchange = $exchange; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $data = $record["formatted"]; + $routingKey = $this->getRoutingKey($record); + + if ($this->exchange instanceof AMQPExchange) { + $this->exchange->publish( + $data, + $routingKey, + 0, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ) + ); + } else { + $this->exchange->basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $routingKey + ); + } + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records) + { + if ($this->exchange instanceof AMQPExchange) { + parent::handleBatch($records); + + return; + } + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + $record = $this->processRecord($record); + $data = $this->getFormatter()->format($record); + + $this->exchange->batch_basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $this->getRoutingKey($record) + ); + } + + $this->exchange->publish_batch(); + } + + /** + * Gets the routing key for the AMQP exchange + * + * @param array $record + * @return string + */ + protected function getRoutingKey(array $record) + { + $routingKey = sprintf( + '%s.%s', + // TODO 2.0 remove substr call + substr($record['level_name'], 0, 4), + $record['channel'] + ); + + return strtolower($routingKey); + } + + /** + * @param string $data + * @return AMQPMessage + */ + private function createAmqpMessage($data) + { + return new AMQPMessage( + (string) $data, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ) + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php new file mode 100644 index 00000000..b3a21bd4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.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 Monolog\Handler; + +use Monolog\Formatter\LineFormatter; + +/** + * Handler sending logs to browser's javascript console with no browser extension required + * + * @author Olivier Poitrey + */ +class BrowserConsoleHandler extends AbstractProcessingHandler +{ + protected static $initialized = false; + protected static $records = array(); + + /** + * {@inheritDoc} + * + * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format. + * + * Example of formatted string: + * + * You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%'); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + // Accumulate records + self::$records[] = $record; + + // Register shutdown handler if not already done + if (!self::$initialized) { + self::$initialized = true; + $this->registerShutdownFunction(); + } + } + + /** + * Convert records to javascript console commands and send it to the browser. + * This method is automatically called on PHP shutdown if output is HTML or Javascript. + */ + public static function send() + { + $format = self::getResponseFormat(); + if ($format === 'unknown') { + return; + } + + if (count(self::$records)) { + if ($format === 'html') { + self::writeOutput(''); + } elseif ($format === 'js') { + self::writeOutput(self::generateScript()); + } + self::reset(); + } + } + + /** + * Forget all logged records + */ + public static function reset() + { + self::$records = array(); + } + + /** + * Wrapper for register_shutdown_function to allow overriding + */ + protected function registerShutdownFunction() + { + if (PHP_SAPI !== 'cli') { + register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send')); + } + } + + /** + * Wrapper for echo to allow overriding + * + * @param string $str + */ + protected static function writeOutput($str) + { + echo $str; + } + + /** + * Checks the format of the response + * + * If Content-Type is set to application/javascript or text/javascript -> js + * If Content-Type is set to text/html, or is unset -> html + * If Content-Type is anything else -> unknown + * + * @return string One of 'js', 'html' or 'unknown' + */ + protected static function getResponseFormat() + { + // Check content type + foreach (headers_list() as $header) { + if (stripos($header, 'content-type:') === 0) { + // This handler only works with HTML and javascript outputs + // text/javascript is obsolete in favour of application/javascript, but still used + if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) { + return 'js'; + } + if (stripos($header, 'text/html') === false) { + return 'unknown'; + } + break; + } + } + + return 'html'; + } + + private static function generateScript() + { + $script = array(); + foreach (self::$records as $record) { + $context = self::dump('Context', $record['context']); + $extra = self::dump('Extra', $record['extra']); + + if (empty($context) && empty($extra)) { + $script[] = self::call_array('log', self::handleStyles($record['formatted'])); + } else { + $script = array_merge($script, + array(self::call_array('groupCollapsed', self::handleStyles($record['formatted']))), + $context, + $extra, + array(self::call('groupEnd')) + ); + } + } + + return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);"; + } + + private static function handleStyles($formatted) + { + $args = array(self::quote('font-weight: normal')); + $format = '%c' . $formatted; + preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + + foreach (array_reverse($matches) as $match) { + $args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0])); + $args[] = '"font-weight: normal"'; + + $pos = $match[0][1]; + $format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0])); + } + + array_unshift($args, self::quote($format)); + + return $args; + } + + private static function handleCustomStyles($style, $string) + { + static $colors = array('blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey'); + static $labels = array(); + + return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function ($m) use ($string, &$colors, &$labels) { + if (trim($m[1]) === 'autolabel') { + // Format the string as a label with consistent auto assigned background color + if (!isset($labels[$string])) { + $labels[$string] = $colors[count($labels) % count($colors)]; + } + $color = $labels[$string]; + + return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px"; + } + + return $m[1]; + }, $style); + } + + private static function dump($title, array $dict) + { + $script = array(); + $dict = array_filter($dict); + if (empty($dict)) { + return $script; + } + $script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title)); + foreach ($dict as $key => $value) { + $value = json_encode($value); + if (empty($value)) { + $value = self::quote(''); + } + $script[] = self::call('log', self::quote('%s: %o'), self::quote($key), $value); + } + + return $script; + } + + private static function quote($arg) + { + return '"' . addcslashes($arg, "\"\n\\") . '"'; + } + + private static function call() + { + $args = func_get_args(); + $method = array_shift($args); + + return self::call_array($method, $args); + } + + private static function call_array($method, array $args) + { + return 'c.' . $method . '(' . implode(', ', $args) . ');'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php new file mode 100644 index 00000000..72f89535 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Buffers all records until closing the handler and then pass them as batch. + * + * This is useful for a MailHandler to send only one mail per request instead of + * sending one per log message. + * + * @author Christophe Coevoet + */ +class BufferHandler extends AbstractHandler +{ + protected $handler; + protected $bufferSize = 0; + protected $bufferLimit; + protected $flushOnOverflow; + protected $buffer = array(); + protected $initialized = false; + + /** + * @param HandlerInterface $handler Handler. + * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded + */ + public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false) + { + parent::__construct($level, $bubble); + $this->handler = $handler; + $this->bufferLimit = (int) $bufferLimit; + $this->flushOnOverflow = $flushOnOverflow; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function(array($this, 'close')); + $this->initialized = true; + } + + if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) { + if ($this->flushOnOverflow) { + $this->flush(); + } else { + array_shift($this->buffer); + $this->bufferSize--; + } + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->buffer[] = $record; + $this->bufferSize++; + + return false === $this->bubble; + } + + public function flush() + { + if ($this->bufferSize === 0) { + return; + } + + $this->handler->handleBatch($this->buffer); + $this->clear(); + } + + public function __destruct() + { + // suppress the parent behavior since we already have register_shutdown_function() + // to call close(), and the reference contained there will prevent this from being + // GC'd until the end of the request + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->flush(); + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + */ + public function clear() + { + $this->bufferSize = 0; + $this->buffer = array(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php new file mode 100644 index 00000000..b00fa844 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\ChromePHPFormatter; +use Monolog\Logger; + +/** + * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/) + * + * This also works out of the box with Firefox 43+ + * + * @author Christophe Coevoet + */ +class ChromePHPHandler extends AbstractProcessingHandler +{ + /** + * Version of the extension + */ + const VERSION = '4.0'; + + /** + * Header name + */ + const HEADER_NAME = 'X-ChromeLogger-Data'; + + /** + * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) + */ + const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; + + protected static $initialized = false; + + /** + * Tracks whether we sent too much data + * + * Chrome limits the headers to 256KB, so when we sent 240KB we stop sending + * + * @var Boolean + */ + protected static $overflowed = false; + + protected static $json = array( + 'version' => self::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array(), + ); + + protected static $sendHeaders = true; + + /** + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s ChromePHPHandler'); + } + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $messages = $this->getFormatter()->formatBatch($messages); + self::$json['rows'] = array_merge(self::$json['rows'], $messages); + $this->send(); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new ChromePHPFormatter(); + } + + /** + * Creates & sends header for a record + * + * @see sendHeader() + * @see send() + * @param array $record + */ + protected function write(array $record) + { + self::$json['rows'][] = $record['formatted']; + + $this->send(); + } + + /** + * Sends the log header + * + * @see sendHeader() + */ + protected function send() + { + if (self::$overflowed || !self::$sendHeaders) { + return; + } + + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + } + + $json = @json_encode(self::$json); + $data = base64_encode(utf8_encode($json)); + if (strlen($data) > 240 * 1024) { + self::$overflowed = true; + + $record = array( + 'message' => 'Incomplete logs, chrome header size limit reached', + 'context' => array(), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'monolog', + 'datetime' => new \DateTime(), + 'extra' => array(), + ); + self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); + $json = @json_encode(self::$json); + $data = base64_encode(utf8_encode($json)); + } + + if (trim($data) !== '') { + $this->sendHeader(self::HEADER_NAME, $data); + } + } + + /** + * Send header string to the client + * + * @param string $header + * @param string $content + */ + protected function sendHeader($header, $content) + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + * + * @return Boolean + */ + protected function headersAccepted() + { + if (empty($_SERVER['HTTP_USER_AGENT'])) { + return false; + } + + return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']); + } + + /** + * BC getter for the sendHeaders property that has been made static + */ + public function __get($property) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + return static::$sendHeaders; + } + + /** + * BC setter for the sendHeaders property that has been made static + */ + public function __set($property, $value) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + static::$sendHeaders = $value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php new file mode 100644 index 00000000..cc986971 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.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 Monolog\Handler; + +use Monolog\Formatter\JsonFormatter; +use Monolog\Logger; + +/** + * CouchDB handler + * + * @author Markus Bachmann + */ +class CouchDBHandler extends AbstractProcessingHandler +{ + private $options; + + public function __construct(array $options = array(), $level = Logger::DEBUG, $bubble = true) + { + $this->options = array_merge(array( + 'host' => 'localhost', + 'port' => 5984, + 'dbname' => 'logger', + 'username' => null, + 'password' => null, + ), $options); + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $basicAuth = null; + if ($this->options['username']) { + $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']); + } + + $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; + $context = stream_context_create(array( + 'http' => array( + 'method' => 'POST', + 'content' => $record['formatted'], + 'ignore_errors' => true, + 'max_redirects' => 0, + 'header' => 'Content-type: application/json', + ), + )); + + if (false === @file_get_contents($url, null, $context)) { + throw new \RuntimeException(sprintf('Could not connect to %s', $url)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php new file mode 100644 index 00000000..96b3ca0c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Logs to Cube. + * + * @link http://square.github.com/cube/ + * @author Wan Chen + */ +class CubeHandler extends AbstractProcessingHandler +{ + private $udpConnection; + private $httpConnection; + private $scheme; + private $host; + private $port; + private $acceptedSchemes = array('http', 'udp'); + + /** + * Create a Cube handler + * + * @throws \UnexpectedValueException when given url is not a valid url. + * A valid url must consist of three parts : protocol://host:port + * Only valid protocols used by Cube are http and udp + */ + public function __construct($url, $level = Logger::DEBUG, $bubble = true) + { + $urlInfo = parse_url($url); + + if (!isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { + throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); + } + + if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) { + throw new \UnexpectedValueException( + 'Invalid protocol (' . $urlInfo['scheme'] . ').' + . ' Valid options are ' . implode(', ', $this->acceptedSchemes)); + } + + $this->scheme = $urlInfo['scheme']; + $this->host = $urlInfo['host']; + $this->port = $urlInfo['port']; + + parent::__construct($level, $bubble); + } + + /** + * Establish a connection to an UDP socket + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when there is no socket extension + */ + protected function connectUdp() + { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); + } + + $this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); + if (!$this->udpConnection) { + throw new \LogicException('Unable to create a socket'); + } + + if (!socket_connect($this->udpConnection, $this->host, $this->port)) { + throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); + } + } + + /** + * Establish a connection to a http server + * @throws \LogicException when no curl extension + */ + protected function connectHttp() + { + if (!extension_loaded('curl')) { + throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler'); + } + + $this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); + + if (!$this->httpConnection) { + throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); + } + + curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $date = $record['datetime']; + + $data = array('time' => $date->format('Y-m-d\TH:i:s.uO')); + unset($record['datetime']); + + if (isset($record['context']['type'])) { + $data['type'] = $record['context']['type']; + unset($record['context']['type']); + } else { + $data['type'] = $record['channel']; + } + + $data['data'] = $record['context']; + $data['data']['level'] = $record['level']; + + if ($this->scheme === 'http') { + $this->writeHttp(json_encode($data)); + } else { + $this->writeUdp(json_encode($data)); + } + } + + private function writeUdp($data) + { + if (!$this->udpConnection) { + $this->connectUdp(); + } + + socket_send($this->udpConnection, $data, strlen($data), 0); + } + + private function writeHttp($data) + { + if (!$this->httpConnection) { + $this->connectHttp(); + } + + curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); + curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array( + 'Content-Type: application/json', + 'Content-Length: ' . strlen('['.$data.']'), + )); + + Curl\Util::execute($this->httpConnection, 5, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php new file mode 100644 index 00000000..48d30b35 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.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 Monolog\Handler\Curl; + +class Util +{ + private static $retriableErrorCodes = array( + CURLE_COULDNT_RESOLVE_HOST, + CURLE_COULDNT_CONNECT, + CURLE_HTTP_NOT_FOUND, + CURLE_READ_ERROR, + CURLE_OPERATION_TIMEOUTED, + CURLE_HTTP_POST_ERROR, + CURLE_SSL_CONNECT_ERROR, + ); + + /** + * Executes a CURL request with optional retries and exception on failure + * + * @param resource $ch curl handler + * @throws \RuntimeException + */ + public static function execute($ch, $retries = 5, $closeAfterDone = true) + { + while ($retries--) { + if (curl_exec($ch) === false) { + $curlErrno = curl_errno($ch); + + if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || !$retries) { + $curlError = curl_error($ch); + + if ($closeAfterDone) { + curl_close($ch); + } + + throw new \RuntimeException(sprintf('Curl error (code %s): %s', $curlErrno, $curlError)); + } + + continue; + } + + if ($closeAfterDone) { + curl_close($ch); + } + break; + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php new file mode 100644 index 00000000..7778c22a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.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 Monolog\Handler; + +use Monolog\Logger; + +/** + * Simple handler wrapper that deduplicates log records across multiple requests + * + * It also includes the BufferHandler functionality and will buffer + * all messages until the end of the request or flush() is called. + * + * This works by storing all log records' messages above $deduplicationLevel + * to the file specified by $deduplicationStore. When further logs come in at the end of the + * request (or when flush() is called), all those above $deduplicationLevel are checked + * against the existing stored logs. If they match and the timestamps in the stored log is + * not older than $time seconds, the new log record is discarded. If no log record is new, the + * whole data set is discarded. + * + * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers + * that send messages to people, to avoid spamming with the same message over and over in case of + * a major component failure like a database server being down which makes all requests fail in the + * same way. + * + * @author Jordi Boggiano + */ +class DeduplicationHandler extends BufferHandler +{ + /** + * @var string + */ + protected $deduplicationStore; + + /** + * @var int + */ + protected $deduplicationLevel; + + /** + * @var int + */ + protected $time; + + /** + * @var bool + */ + private $gc = false; + + /** + * @param HandlerInterface $handler Handler. + * @param string $deduplicationStore The file/path where the deduplication log should be kept + * @param int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes + * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true) + { + parent::__construct($handler, 0, Logger::DEBUG, $bubble, false); + + $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore; + $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel); + $this->time = $time; + } + + public function flush() + { + if ($this->bufferSize === 0) { + return; + } + + $passthru = null; + + foreach ($this->buffer as $record) { + if ($record['level'] >= $this->deduplicationLevel) { + + $passthru = $passthru || !$this->isDuplicate($record); + if ($passthru) { + $this->appendRecord($record); + } + } + } + + // default of null is valid as well as if no record matches duplicationLevel we just pass through + if ($passthru === true || $passthru === null) { + $this->handler->handleBatch($this->buffer); + } + + $this->clear(); + + if ($this->gc) { + $this->collectLogs(); + } + } + + private function isDuplicate(array $record) + { + if (!file_exists($this->deduplicationStore)) { + return false; + } + + $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($store)) { + return false; + } + + $yesterday = time() - 86400; + $timestampValidity = $record['datetime']->getTimestamp() - $this->time; + $expectedMessage = preg_replace('{[\r\n].*}', '', $record['message']); + + for ($i = count($store) - 1; $i >= 0; $i--) { + list($timestamp, $level, $message) = explode(':', $store[$i], 3); + + if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) { + return true; + } + + if ($timestamp < $yesterday) { + $this->gc = true; + } + } + + return false; + } + + private function collectLogs() + { + if (!file_exists($this->deduplicationStore)) { + return false; + } + + $handle = fopen($this->deduplicationStore, 'rw+'); + flock($handle, LOCK_EX); + $validLogs = array(); + + $timestampValidity = time() - $this->time; + + while (!feof($handle)) { + $log = fgets($handle); + if (substr($log, 0, 10) >= $timestampValidity) { + $validLogs[] = $log; + } + } + + ftruncate($handle, 0); + rewind($handle); + foreach ($validLogs as $log) { + fwrite($handle, $log); + } + + flock($handle, LOCK_UN); + fclose($handle); + + $this->gc = false; + } + + private function appendRecord(array $record) + { + file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php new file mode 100644 index 00000000..b91ffec9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.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 Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; +use Doctrine\CouchDB\CouchDBClient; + +/** + * CouchDB handler for Doctrine CouchDB ODM + * + * @author Markus Bachmann + */ +class DoctrineCouchDBHandler extends AbstractProcessingHandler +{ + private $client; + + public function __construct(CouchDBClient $client, $level = Logger::DEBUG, $bubble = true) + { + $this->client = $client; + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $this->client->postDocument($record['formatted']); + } + + protected function getDefaultFormatter() + { + return new NormalizerFormatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php new file mode 100644 index 00000000..ad1011d3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.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 Monolog\Handler; + +use Aws\Common\Aws; +use Aws\DynamoDb\DynamoDbClient; +use Aws\DynamoDb\Marshaler; +use Monolog\Formatter\ScalarFormatter; +use Monolog\Logger; + +/** + * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/) + * + * @link https://github.com/aws/aws-sdk-php/ + * @author Andrew Lawson + */ +class DynamoDbHandler extends AbstractProcessingHandler +{ + const DATE_FORMAT = 'Y-m-d\TH:i:s.uO'; + + /** + * @var DynamoDbClient + */ + protected $client; + + /** + * @var string + */ + protected $table; + + /** + * @var int + */ + protected $version; + + /** + * @var Marshaler + */ + protected $marshaler; + + /** + * @param DynamoDbClient $client + * @param string $table + * @param int $level + * @param bool $bubble + */ + public function __construct(DynamoDbClient $client, $table, $level = Logger::DEBUG, $bubble = true) + { + if (defined('Aws\Common\Aws::VERSION') && version_compare(Aws::VERSION, '3.0', '>=')) { + $this->version = 3; + $this->marshaler = new Marshaler; + } else { + $this->version = 2; + } + + $this->client = $client; + $this->table = $table; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $filtered = $this->filterEmptyFields($record['formatted']); + if ($this->version === 3) { + $formatted = $this->marshaler->marshalItem($filtered); + } else { + $formatted = $this->client->formatAttributes($filtered); + } + + $this->client->putItem(array( + 'TableName' => $this->table, + 'Item' => $formatted, + )); + } + + /** + * @param array $record + * @return array + */ + protected function filterEmptyFields(array $record) + { + return array_filter($record, function ($value) { + return !empty($value) || false === $value || 0 === $value; + }); + } + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter() + { + return new ScalarFormatter(self::DATE_FORMAT); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php new file mode 100644 index 00000000..81967406 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.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 Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Logger; +use Elastica\Client; +use Elastica\Exception\ExceptionInterface; + +/** + * Elastic Search handler + * + * Usage example: + * + * $client = new \Elastica\Client(); + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', + * ); + * $handler = new ElasticSearchHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Jelle Vink + */ +class ElasticSearchHandler extends AbstractProcessingHandler +{ + /** + * @var Client + */ + protected $client; + + /** + * @var array Handler config options + */ + protected $options = array(); + + /** + * @param Client $client Elastica Client object + * @param array $options Handler configuration + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(Client $client, array $options = array(), $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + array( + 'index' => 'monolog', // Elastic index name + 'type' => 'record', // Elastic document type + 'ignore_error' => false, // Suppress Elastica exceptions + ), + $options + ); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $this->bulkSend(array($record['formatted'])); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + if ($formatter instanceof ElasticaFormatter) { + return parent::setFormatter($formatter); + } + throw new \InvalidArgumentException('ElasticSearchHandler is only compatible with ElasticaFormatter'); + } + + /** + * Getter options + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new ElasticaFormatter($this->options['index'], $this->options['type']); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * @param array $documents + * @throws \RuntimeException + */ + protected function bulkSend(array $documents) + { + try { + $this->client->addDocuments($documents); + } catch (ExceptionInterface $e) { + if (!$this->options['ignore_error']) { + throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php new file mode 100644 index 00000000..1447a584 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.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 Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Stores to PHP error_log() handler. + * + * @author Elan Ruusamäe + */ +class ErrorLogHandler extends AbstractProcessingHandler +{ + const OPERATING_SYSTEM = 0; + const SAPI = 4; + + protected $messageType; + protected $expandNewlines; + + /** + * @param int $messageType Says where the error should go. + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries + */ + public function __construct($messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, $bubble = true, $expandNewlines = false) + { + parent::__construct($level, $bubble); + + if (false === in_array($messageType, self::getAvailableTypes())) { + $message = sprintf('The given message type "%s" is not supported', print_r($messageType, true)); + throw new \InvalidArgumentException($message); + } + + $this->messageType = $messageType; + $this->expandNewlines = $expandNewlines; + } + + /** + * @return array With all available types + */ + public static function getAvailableTypes() + { + return array( + self::OPERATING_SYSTEM, + self::SAPI, + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%'); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if ($this->expandNewlines) { + $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); + foreach ($lines as $line) { + error_log($line, $this->messageType); + } + } else { + error_log((string) $record['formatted'], $this->messageType); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php new file mode 100644 index 00000000..2a0f7fd1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.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 Monolog\Handler; + +use Monolog\Logger; + +/** + * Simple handler wrapper that filters records based on a list of levels + * + * It can be configured with an exact list of levels to allow, or a min/max level. + * + * @author Hennadiy Verkh + * @author Jordi Boggiano + */ +class FilterHandler extends AbstractHandler +{ + /** + * Handler or factory callable($record, $this) + * + * @var callable|\Monolog\Handler\HandlerInterface + */ + protected $handler; + + /** + * Minimum level for logs that are passed to handler + * + * @var int[] + */ + protected $acceptedLevels; + + /** + * Whether the messages that are handled can bubble up the stack or not + * + * @var Boolean + */ + protected $bubble; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record, $this). + * @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided + * @param int $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, $bubble = true) + { + $this->handler = $handler; + $this->bubble = $bubble; + $this->setAcceptedLevels($minLevelOrList, $maxLevel); + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + /** + * @return array + */ + public function getAcceptedLevels() + { + return array_flip($this->acceptedLevels); + } + + /** + * @param int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided + * @param int|string $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array + */ + public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY) + { + if (is_array($minLevelOrList)) { + $acceptedLevels = array_map('Monolog\Logger::toMonologLevel', $minLevelOrList); + } else { + $minLevelOrList = Logger::toMonologLevel($minLevelOrList); + $maxLevel = Logger::toMonologLevel($maxLevel); + $acceptedLevels = array_values(array_filter(Logger::getLevels(), function ($level) use ($minLevelOrList, $maxLevel) { + return $level >= $minLevelOrList && $level <= $maxLevel; + })); + } + $this->acceptedLevels = array_flip($acceptedLevels); + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return isset($this->acceptedLevels[$record['level']]); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + // The same logic as in FingersCrossedHandler + if (!$this->handler instanceof HandlerInterface) { + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->handler->handle($record); + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $filtered = array(); + foreach ($records as $record) { + if ($this->isHandling($record)) { + $filtered[] = $record; + } + } + + $this->handler->handleBatch($filtered); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php new file mode 100644 index 00000000..c3e42efe --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.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 Monolog\Handler\FingersCrossed; + +/** + * Interface for activation strategies for the FingersCrossedHandler. + * + * @author Johannes M. Schmitt + */ +interface ActivationStrategyInterface +{ + /** + * Returns whether the given record activates the handler. + * + * @param array $record + * @return Boolean + */ + public function isHandlerActivated(array $record); +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php new file mode 100644 index 00000000..2a2a64d9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.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 Monolog\Handler\FingersCrossed; + +use Monolog\Logger; + +/** + * Channel and Error level based monolog activation strategy. Allows to trigger activation + * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except + * for records of the 'sql' channel; those should trigger activation on level 'WARN'. + * + * Example: + * + * + * $activationStrategy = new ChannelLevelActivationStrategy( + * Logger::CRITICAL, + * array( + * 'request' => Logger::ALERT, + * 'sensitive' => Logger::ERROR, + * ) + * ); + * $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy); + * + * + * @author Mike Meessen + */ +class ChannelLevelActivationStrategy implements ActivationStrategyInterface +{ + private $defaultActionLevel; + private $channelToActionLevel; + + /** + * @param int $defaultActionLevel The default action level to be used if the record's category doesn't match any + * @param array $channelToActionLevel An array that maps channel names to action levels. + */ + public function __construct($defaultActionLevel, $channelToActionLevel = array()) + { + $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel); + $this->channelToActionLevel = array_map('Monolog\Logger::toMonologLevel', $channelToActionLevel); + } + + public function isHandlerActivated(array $record) + { + if (isset($this->channelToActionLevel[$record['channel']])) { + return $record['level'] >= $this->channelToActionLevel[$record['channel']]; + } + + return $record['level'] >= $this->defaultActionLevel; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php new file mode 100644 index 00000000..6e630852 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.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 Monolog\Handler\FingersCrossed; + +use Monolog\Logger; + +/** + * Error level based activation strategy. + * + * @author Johannes M. Schmitt + */ +class ErrorLevelActivationStrategy implements ActivationStrategyInterface +{ + private $actionLevel; + + public function __construct($actionLevel) + { + $this->actionLevel = Logger::toMonologLevel($actionLevel); + } + + public function isHandlerActivated(array $record) + { + return $record['level'] >= $this->actionLevel; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php new file mode 100644 index 00000000..d1dcaacf --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; +use Monolog\Logger; + +/** + * Buffers all records until a certain level is reached + * + * The advantage of this approach is that you don't get any clutter in your log files. + * Only requests which actually trigger an error (or whatever your actionLevel is) will be + * in the logs, but they will contain all records, not only those above the level threshold. + * + * You can find the various activation strategies in the + * Monolog\Handler\FingersCrossed\ namespace. + * + * @author Jordi Boggiano + */ +class FingersCrossedHandler extends AbstractHandler +{ + protected $handler; + protected $activationStrategy; + protected $buffering = true; + protected $bufferSize; + protected $buffer = array(); + protected $stopBuffering; + protected $passthruLevel; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler). + * @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action + * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $stopBuffering Whether the handler should stop buffering after being triggered (default true) + * @param int $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered + */ + public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true, $passthruLevel = null) + { + if (null === $activationStrategy) { + $activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING); + } + + // convert simple int activationStrategy to an object + if (!$activationStrategy instanceof ActivationStrategyInterface) { + $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy); + } + + $this->handler = $handler; + $this->activationStrategy = $activationStrategy; + $this->bufferSize = $bufferSize; + $this->bubble = $bubble; + $this->stopBuffering = $stopBuffering; + + if ($passthruLevel !== null) { + $this->passthruLevel = Logger::toMonologLevel($passthruLevel); + } + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return true; + } + + /** + * Manually activate this logger regardless of the activation strategy + */ + public function activate() + { + if ($this->stopBuffering) { + $this->buffering = false; + } + if (!$this->handler instanceof HandlerInterface) { + $record = end($this->buffer) ?: null; + + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + $this->handler->handleBatch($this->buffer); + $this->buffer = array(); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + if ($this->buffering) { + $this->buffer[] = $record; + if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) { + array_shift($this->buffer); + } + if ($this->activationStrategy->isHandlerActivated($record)) { + $this->activate(); + } + } else { + $this->handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function close() + { + if (null !== $this->passthruLevel) { + $level = $this->passthruLevel; + $this->buffer = array_filter($this->buffer, function ($record) use ($level) { + return $record['level'] >= $level; + }); + if (count($this->buffer) > 0) { + $this->handler->handleBatch($this->buffer); + $this->buffer = array(); + } + } + } + + /** + * Resets the state of the handler. Stops forwarding records to the wrapped handler. + */ + public function reset() + { + $this->buffering = true; + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + * + * It also resets the handler to its initial buffering state. + */ + public function clear() + { + $this->buffer = array(); + $this->reset(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php new file mode 100644 index 00000000..fee47950 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\WildfireFormatter; + +/** + * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. + * + * @author Eric Clemmons (@ericclemmons) + */ +class FirePHPHandler extends AbstractProcessingHandler +{ + /** + * WildFire JSON header message format + */ + const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + + /** + * FirePHP structure for parsing messages & their presentation + */ + const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + + /** + * Must reference a "known" plugin, otherwise headers won't display in FirePHP + */ + const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; + + /** + * Header prefix for Wildfire to recognize & parse headers + */ + const HEADER_PREFIX = 'X-Wf'; + + /** + * Whether or not Wildfire vendor-specific headers have been generated & sent yet + */ + protected static $initialized = false; + + /** + * Shared static message index between potentially multiple handlers + * @var int + */ + protected static $messageIndex = 1; + + protected static $sendHeaders = true; + + /** + * Base header creation function used by init headers & record headers + * + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * @return array Complete header string ready for the client as key and message as value + */ + protected function createHeader(array $meta, $message) + { + $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta)); + + return array($header => $message); + } + + /** + * Creates message header from record + * + * @see createHeader() + * @param array $record + * @return string + */ + protected function createRecordHeader(array $record) + { + // Wildfire is extensible to support multiple protocols & plugins in a single request, + // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. + return $this->createHeader( + array(1, 1, 1, self::$messageIndex++), + $record['formatted'] + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new WildfireFormatter(); + } + + /** + * Wildfire initialization headers to enable message parsing + * + * @see createHeader() + * @see sendHeader() + * @return array + */ + protected function getInitHeaders() + { + // Initial payload consists of required headers for Wildfire + return array_merge( + $this->createHeader(array('Protocol', 1), self::PROTOCOL_URI), + $this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI), + $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI) + ); + } + + /** + * Send header string to the client + * + * @param string $header + * @param string $content + */ + protected function sendHeader($header, $content) + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Creates & sends header for a record, ensuring init headers have been sent prior + * + * @see sendHeader() + * @see sendInitHeaders() + * @param array $record + */ + protected function write(array $record) + { + if (!self::$sendHeaders) { + return; + } + + // WildFire-specific headers must be sent prior to any messages + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + foreach ($this->getInitHeaders() as $header => $content) { + $this->sendHeader($header, $content); + } + } + + $header = $this->createRecordHeader($record); + if (trim(current($header)) !== '') { + $this->sendHeader(key($header), current($header)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + * + * @return Boolean + */ + protected function headersAccepted() + { + if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) { + return true; + } + + return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); + } + + /** + * BC getter for the sendHeaders property that has been made static + */ + public function __get($property) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + return static::$sendHeaders; + } + + /** + * BC setter for the sendHeaders property that has been made static + */ + public function __set($property, $value) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + static::$sendHeaders = $value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php new file mode 100644 index 00000000..c43c0134 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.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 Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Sends logs to Fleep.io using Webhook integrations + * + * You'll need a Fleep.io account to use this handler. + * + * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation + * @author Ando Roots + */ +class FleepHookHandler extends SocketHandler +{ + const FLEEP_HOST = 'fleep.io'; + + const FLEEP_HOOK_URI = '/hook/'; + + /** + * @var string Webhook token (specifies the conversation where logs are sent) + */ + protected $token; + + /** + * Construct a new Fleep.io Handler. + * + * For instructions on how to create a new web hook in your conversations + * see https://fleep.io/integrations/webhooks/ + * + * @param string $token Webhook token + * @param bool|int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @throws MissingExtensionException + */ + public function __construct($token, $level = Logger::DEBUG, $bubble = true) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler'); + } + + $this->token = $token; + + $connectionString = 'ssl://' . self::FLEEP_HOST . ':443'; + parent::__construct($connectionString, $level, $bubble); + } + + /** + * Returns the default formatter to use with this handler + * + * Overloaded to remove empty context and extra arrays from the end of the log message. + * + * @return LineFormatter + */ + protected function getDefaultFormatter() + { + return new LineFormatter(null, null, true, true); + } + + /** + * Handles a log record + * + * @param array $record + */ + public function write(array $record) + { + parent::write($record); + $this->closeSocket(); + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST " . self::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; + $header .= "Host: " . self::FLEEP_HOST . "\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = array( + 'message' => $record['formatted'], + ); + + return http_build_query($dataArray); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php new file mode 100644 index 00000000..dd9a361c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FlowdockFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Sends notifications through the Flowdock push API + * + * This must be configured with a FlowdockFormatter instance via setFormatter() + * + * Notes: + * API token - Flowdock API token + * + * @author Dominik Liebler + * @see https://www.flowdock.com/api/push + */ +class FlowdockHandler extends SocketHandler +{ + /** + * @var string + */ + protected $apiToken; + + /** + * @param string $apiToken + * @param bool|int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @throws MissingExtensionException if OpenSSL is missing + */ + public function __construct($apiToken, $level = Logger::DEBUG, $bubble = true) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler'); + } + + parent::__construct('ssl://api.flowdock.com:443', $level, $bubble); + $this->apiToken = $apiToken; + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + if (!$formatter instanceof FlowdockFormatter) { + throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + return parent::setFormatter($formatter); + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + parent::write($record); + + $this->closeSocket(); + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + return json_encode($record['formatted']['flowdock']); + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n"; + $header .= "Host: api.flowdock.com\r\n"; + $header .= "Content-Type: application/json\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php new file mode 100644 index 00000000..d3847d82 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.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 Monolog\Handler; + +use Gelf\IMessagePublisher; +use Gelf\PublisherInterface; +use Gelf\Publisher; +use InvalidArgumentException; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +/** + * Handler to send messages to a Graylog2 (http://www.graylog2.org) server + * + * @author Matt Lehner + * @author Benjamin Zikarsky + */ +class GelfHandler extends AbstractProcessingHandler +{ + /** + * @var Publisher the publisher object that sends the message to the server + */ + protected $publisher; + + /** + * @param PublisherInterface|IMessagePublisher|Publisher $publisher a publisher object + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($publisher, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!$publisher instanceof Publisher && !$publisher instanceof IMessagePublisher && !$publisher instanceof PublisherInterface) { + throw new InvalidArgumentException('Invalid publisher, expected a Gelf\Publisher, Gelf\IMessagePublisher or Gelf\PublisherInterface instance'); + } + + $this->publisher = $publisher; + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->publisher = null; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->publisher->publish($record['formatted']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new GelfMessageFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php new file mode 100644 index 00000000..663f5a92 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.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 Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Forwards records to multiple handlers + * + * @author Lenar Lõhmus + */ +class GroupHandler extends AbstractHandler +{ + protected $handlers; + + /** + * @param array $handlers Array of Handlers. + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(array $handlers, $bubble = true) + { + foreach ($handlers as $handler) { + if (!$handler instanceof HandlerInterface) { + throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.'); + } + } + + $this->handlers = $handlers; + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + foreach ($this->handlers as $handler) { + $handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + if ($this->processors) { + $processed = array(); + foreach ($records as $record) { + foreach ($this->processors as $processor) { + $processed[] = call_user_func($processor, $record); + } + } + $records = $processed; + } + + foreach ($this->handlers as $handler) { + $handler->handleBatch($records); + } + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + foreach ($this->handlers as $handler) { + $handler->setFormatter($formatter); + } + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php new file mode 100644 index 00000000..d920c4ba --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface that all Monolog Handlers must implement + * + * @author Jordi Boggiano + */ +interface HandlerInterface +{ + /** + * Checks whether the given record will be handled by this handler. + * + * This is mostly done for performance reasons, to avoid calling processors for nothing. + * + * Handlers should still check the record levels within handle(), returning false in isHandling() + * is no guarantee that handle() will not be called, and isHandling() might not be called + * for a given record. + * + * @param array $record Partial log record containing only a level key + * + * @return Boolean + */ + public function isHandling(array $record); + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * @param array $record The record to handle + * @return Boolean true means that this handler handled the record, and that bubbling is not permitted. + * false means the record was either not processed or that this handler allows bubbling. + */ + public function handle(array $record); + + /** + * Handles a set of records at once. + * + * @param array $records The records to handle (an array of record arrays) + */ + public function handleBatch(array $records); + + /** + * Adds a processor in the stack. + * + * @param callable $callback + * @return self + */ + public function pushProcessor($callback); + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor(); + + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + * @return self + */ + public function setFormatter(FormatterInterface $formatter); + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(); +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php new file mode 100644 index 00000000..56bc2704 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * This simple wrapper class can be used to extend handlers functionality. + * + * Example: A filtering handle. Inherit from this class, override isHandling() like this + * + * public function isHandling(array $record) + * { + * if ($record meets certain conditions) { + * return false; + * } + * return $this->handler->isHandling($record); + * } + * + * @author Alexey Karapetov + */ +class HandlerWrapper implements HandlerInterface +{ + /** + * @var HandlerInterface + */ + protected $handler; + + /** + * HandlerWrapper constructor. + * @param HandlerInterface $handler + */ + public function __construct(HandlerInterface $handler) + { + $this->handler = $handler; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $this->handler->isHandling($record); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + return $this->handler->handle($record); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + return $this->handler->handleBatch($records); + } + + /** + * {@inheritdoc} + */ + public function pushProcessor($callback) + { + $this->handler->pushProcessor($callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + return $this->handler->popProcessor(); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->handler->setFormatter($formatter); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->handler->getFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php new file mode 100644 index 00000000..73049f36 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php @@ -0,0 +1,350 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through the hipchat api to a hipchat room + * + * Notes: + * API token - HipChat API token + * Room - HipChat Room Id or name, where messages are sent + * Name - Name used to send the message (from) + * notify - Should the message trigger a notification in the clients + * version - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2) + * + * @author Rafael Dohms + * @see https://www.hipchat.com/docs/api + */ +class HipChatHandler extends SocketHandler +{ + /** + * Use API version 1 + */ + const API_V1 = 'v1'; + + /** + * Use API version v2 + */ + const API_V2 = 'v2'; + + /** + * The maximum allowed length for the name used in the "from" field. + */ + const MAXIMUM_NAME_LENGTH = 15; + + /** + * The maximum allowed length for the message. + */ + const MAXIMUM_MESSAGE_LENGTH = 9500; + + /** + * @var string + */ + private $token; + + /** + * @var string + */ + private $room; + + /** + * @var string + */ + private $name; + + /** + * @var bool + */ + private $notify; + + /** + * @var string + */ + private $format; + + /** + * @var string + */ + private $host; + + /** + * @var string + */ + private $version; + + /** + * @param string $token HipChat API Token + * @param string $room The room that should be alerted of the message (Id or Name) + * @param string $name Name used in the "from" field. + * @param bool $notify Trigger a notification in clients or not + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $useSSL Whether to connect via SSL. + * @param string $format The format of the messages (default to text, can be set to html if you have html in the messages) + * @param string $host The HipChat server hostname. + * @param string $version The HipChat API version (default HipChatHandler::API_V1) + */ + public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1) + { + if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) { + throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.'); + } + + $connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80'; + parent::__construct($connectionString, $level, $bubble); + + $this->token = $token; + $this->name = $name; + $this->notify = $notify; + $this->room = $room; + $this->format = $format; + $this->host = $host; + $this->version = $version; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = array( + 'notify' => $this->version == self::API_V1 ? + ($this->notify ? 1 : 0) : + ($this->notify ? 'true' : 'false'), + 'message' => $record['formatted'], + 'message_format' => $this->format, + 'color' => $this->getAlertColor($record['level']), + ); + + if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) { + if (function_exists('mb_substr')) { + $dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; + } else { + $dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; + } + } + + // if we are using the legacy API then we need to send some additional information + if ($this->version == self::API_V1) { + $dataArray['room_id'] = $this->room; + } + + // append the sender name if it is set + // always append it if we use the v1 api (it is required in v1) + if ($this->version == self::API_V1 || $this->name !== null) { + $dataArray['from'] = (string) $this->name; + } + + return http_build_query($dataArray); + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + if ($this->version == self::API_V1) { + $header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n"; + } else { + // needed for rooms with special (spaces, etc) characters in the name + $room = rawurlencode($this->room); + $header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n"; + } + + $header .= "Host: {$this->host}\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Assigns a color to each level of log records. + * + * @param int $level + * @return string + */ + protected function getAlertColor($level) + { + switch (true) { + case $level >= Logger::ERROR: + return 'red'; + case $level >= Logger::WARNING: + return 'yellow'; + case $level >= Logger::INFO: + return 'green'; + case $level == Logger::DEBUG: + return 'gray'; + default: + return 'yellow'; + } + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + parent::write($record); + $this->closeSocket(); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + if (count($records) == 0) { + return true; + } + + $batchRecords = $this->combineRecords($records); + + $handled = false; + foreach ($batchRecords as $batchRecord) { + if ($this->isHandling($batchRecord)) { + $this->write($batchRecord); + $handled = true; + } + } + + if (!$handled) { + return false; + } + + return false === $this->bubble; + } + + /** + * Combines multiple records into one. Error level of the combined record + * will be the highest level from the given records. Datetime will be taken + * from the first record. + * + * @param $records + * @return array + */ + private function combineRecords($records) + { + $batchRecord = null; + $batchRecords = array(); + $messages = array(); + $formattedMessages = array(); + $level = 0; + $levelName = null; + $datetime = null; + + foreach ($records as $record) { + $record = $this->processRecord($record); + + if ($record['level'] > $level) { + $level = $record['level']; + $levelName = $record['level_name']; + } + + if (null === $datetime) { + $datetime = $record['datetime']; + } + + $messages[] = $record['message']; + $messageStr = implode(PHP_EOL, $messages); + $formattedMessages[] = $this->getFormatter()->format($record); + $formattedMessageStr = implode('', $formattedMessages); + + $batchRecord = array( + 'message' => $messageStr, + 'formatted' => $formattedMessageStr, + 'context' => array(), + 'extra' => array(), + ); + + if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) { + // Pop the last message and implode the remaining messages + $lastMessage = array_pop($messages); + $lastFormattedMessage = array_pop($formattedMessages); + $batchRecord['message'] = implode(PHP_EOL, $messages); + $batchRecord['formatted'] = implode('', $formattedMessages); + + $batchRecords[] = $batchRecord; + $messages = array($lastMessage); + $formattedMessages = array($lastFormattedMessage); + + $batchRecord = null; + } + } + + if (null !== $batchRecord) { + $batchRecords[] = $batchRecord; + } + + // Set the max level and datetime for all records + foreach ($batchRecords as &$batchRecord) { + $batchRecord = array_merge( + $batchRecord, + array( + 'level' => $level, + 'level_name' => $levelName, + 'datetime' => $datetime, + ) + ); + } + + return $batchRecords; + } + + /** + * Validates the length of a string. + * + * If the `mb_strlen()` function is available, it will use that, as HipChat + * allows UTF-8 characters. Otherwise, it will fall back to `strlen()`. + * + * Note that this might cause false failures in the specific case of using + * a valid name with less than 16 characters, but 16 or more bytes, on a + * system where `mb_strlen()` is unavailable. + * + * @param string $str + * @param int $length + * + * @return bool + */ + private function validateStringLength($str, $length) + { + if (function_exists('mb_strlen')) { + return (mb_strlen($str) <= $length); + } + + return (strlen($str) <= $length); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php new file mode 100644 index 00000000..d60a3c82 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.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 Monolog\Handler; + +use Monolog\Logger; + +/** + * IFTTTHandler uses cURL to trigger IFTTT Maker actions + * + * Register a secret key and trigger/event name at https://ifttt.com/maker + * + * value1 will be the channel from monolog's Logger constructor, + * value2 will be the level name (ERROR, WARNING, ..) + * value3 will be the log record's message + * + * @author Nehal Patel + */ +class IFTTTHandler extends AbstractProcessingHandler +{ + private $eventName; + private $secretKey; + + /** + * @param string $eventName The name of the IFTTT Maker event that should be triggered + * @param string $secretKey A valid IFTTT secret key + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($eventName, $secretKey, $level = Logger::ERROR, $bubble = true) + { + $this->eventName = $eventName; + $this->secretKey = $secretKey; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + public function write(array $record) + { + $postData = array( + "value1" => $record["channel"], + "value2" => $record["level_name"], + "value3" => $record["message"], + ); + $postString = json_encode($postData); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + "Content-Type: application/json", + )); + + Curl\Util::execute($ch); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php new file mode 100644 index 00000000..494c605b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.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 Monolog\Handler; + +use Monolog\Logger; + +/** + * @author Robert Kaufmann III + */ +class LogEntriesHandler extends SocketHandler +{ + /** + * @var string + */ + protected $logToken; + + /** + * @param string $token Log token supplied by LogEntries + * @param bool $useSSL Whether or not SSL encryption should be used. + * @param int $level The minimum logging level to trigger this handler + * @param bool $bubble Whether or not messages that are handled should bubble up the stack. + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct($token, $useSSL = true, $level = Logger::DEBUG, $bubble = true) + { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler'); + } + + $endpoint = $useSSL ? 'ssl://data.logentries.com:443' : 'data.logentries.com:80'; + parent::__construct($endpoint, $level, $bubble); + $this->logToken = $token; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + return $this->logToken . ' ' . $record['formatted']; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php new file mode 100644 index 00000000..bcd62e1c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.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 Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LogglyFormatter; + +/** + * Sends errors to Loggly. + * + * @author Przemek Sobstel + * @author Adam Pancutt + * @author Gregory Barchard + */ +class LogglyHandler extends AbstractProcessingHandler +{ + const HOST = 'logs-01.loggly.com'; + const ENDPOINT_SINGLE = 'inputs'; + const ENDPOINT_BATCH = 'bulk'; + + protected $token; + + protected $tag = array(); + + public function __construct($token, $level = Logger::DEBUG, $bubble = true) + { + if (!extension_loaded('curl')) { + throw new \LogicException('The curl extension is needed to use the LogglyHandler'); + } + + $this->token = $token; + + parent::__construct($level, $bubble); + } + + public function setTag($tag) + { + $tag = !empty($tag) ? $tag : array(); + $this->tag = is_array($tag) ? $tag : array($tag); + } + + public function addTag($tag) + { + if (!empty($tag)) { + $tag = is_array($tag) ? $tag : array($tag); + $this->tag = array_unique(array_merge($this->tag, $tag)); + } + } + + protected function write(array $record) + { + $this->send($record["formatted"], self::ENDPOINT_SINGLE); + } + + public function handleBatch(array $records) + { + $level = $this->level; + + $records = array_filter($records, function ($record) use ($level) { + return ($record['level'] >= $level); + }); + + if ($records) { + $this->send($this->getFormatter()->formatBatch($records), self::ENDPOINT_BATCH); + } + } + + protected function send($data, $endpoint) + { + $url = sprintf("https://%s/%s/%s/", self::HOST, $endpoint, $this->token); + + $headers = array('Content-Type: application/json'); + + if (!empty($this->tag)) { + $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag); + } + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + Curl\Util::execute($ch); + } + + protected function getDefaultFormatter() + { + return new LogglyFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php new file mode 100644 index 00000000..9e232838 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.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 Monolog\Handler; + +/** + * Base class for all mail handlers + * + * @author Gyula Sallai + */ +abstract class MailHandler extends AbstractProcessingHandler +{ + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); + } + } + + /** + * Send a mail with the given content + * + * @param string $content formatted email body to be sent + * @param array $records the array of log records that formed this content + */ + abstract protected function send($content, array $records); + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->send((string) $record['formatted'], array($record)); + } + + protected function getHighestRecord(array $records) + { + $highestRecord = null; + foreach ($records as $record) { + if ($highestRecord === null || $highestRecord['level'] < $record['level']) { + $highestRecord = $record; + } + } + + return $highestRecord; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php new file mode 100644 index 00000000..ab95924f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.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 Monolog\Handler; + +use Monolog\Logger; + +/** + * MandrillHandler uses cURL to send the emails to the Mandrill API + * + * @author Adam Nicholson + */ +class MandrillHandler extends MailHandler +{ + protected $message; + protected $apiKey; + + /** + * @param string $apiKey A valid Mandrill API key + * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!$message instanceof \Swift_Message && is_callable($message)) { + $message = call_user_func($message); + } + if (!$message instanceof \Swift_Message) { + throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it'); + } + $this->message = $message; + $this->apiKey = $apiKey; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $message = clone $this->message; + $message->setBody($content); + $message->setDate(time()); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array( + 'key' => $this->apiKey, + 'raw_message' => (string) $message, + 'async' => false, + ))); + + Curl\Util::execute($ch); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php new file mode 100644 index 00000000..4724a7e2 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.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 Monolog\Handler; + +/** + * Exception can be thrown if an extension for an handler is missing + * + * @author Christian Bergau + */ +class MissingExtensionException extends \Exception +{ +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php new file mode 100644 index 00000000..56fe755b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.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 Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; + +/** + * Logs to a MongoDB database. + * + * usage example: + * + * $log = new Logger('application'); + * $mongodb = new MongoDBHandler(new \Mongo("mongodb://localhost:27017"), "logs", "prod"); + * $log->pushHandler($mongodb); + * + * @author Thomas Tourlourat + */ +class MongoDBHandler extends AbstractProcessingHandler +{ + protected $mongoCollection; + + public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true) + { + if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo || $mongo instanceof \MongoDB\Client)) { + throw new \InvalidArgumentException('MongoClient, Mongo or MongoDB\Client instance required'); + } + + $this->mongoCollection = $mongo->selectCollection($database, $collection); + + parent::__construct($level, $bubble); + } + + protected function write(array $record) + { + if ($this->mongoCollection instanceof \MongoDB\Collection) { + $this->mongoCollection->insertOne($record["formatted"]); + } else { + $this->mongoCollection->save($record["formatted"]); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new NormalizerFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php new file mode 100644 index 00000000..d7807fd1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.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 Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * NativeMailerHandler uses the mail() function to send the emails + * + * @author Christophe Coevoet + * @author Mark Garrett + */ +class NativeMailerHandler extends MailHandler +{ + /** + * The email addresses to which the message will be sent + * @var array + */ + protected $to; + + /** + * The subject of the email + * @var string + */ + protected $subject; + + /** + * Optional headers for the message + * @var array + */ + protected $headers = array(); + + /** + * Optional parameters for the message + * @var array + */ + protected $parameters = array(); + + /** + * The wordwrap length for the message + * @var int + */ + protected $maxColumnWidth; + + /** + * The Content-type for the message + * @var string + */ + protected $contentType = 'text/plain'; + + /** + * The encoding for the message + * @var string + */ + protected $encoding = 'utf-8'; + + /** + * @param string|array $to The receiver of the mail + * @param string $subject The subject of the mail + * @param string $from The sender of the mail + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $maxColumnWidth The maximum column width that the message lines will have + */ + public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true, $maxColumnWidth = 70) + { + parent::__construct($level, $bubble); + $this->to = is_array($to) ? $to : array($to); + $this->subject = $subject; + $this->addHeader(sprintf('From: %s', $from)); + $this->maxColumnWidth = $maxColumnWidth; + } + + /** + * Add headers to the message + * + * @param string|array $headers Custom added headers + * @return self + */ + public function addHeader($headers) + { + foreach ((array) $headers as $header) { + if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { + throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons'); + } + $this->headers[] = $header; + } + + return $this; + } + + /** + * Add parameters to the message + * + * @param string|array $parameters Custom added parameters + * @return self + */ + public function addParameter($parameters) + { + $this->parameters = array_merge($this->parameters, (array) $parameters); + + return $this; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $content = wordwrap($content, $this->maxColumnWidth); + $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n"); + $headers .= 'Content-type: ' . $this->getContentType() . '; charset=' . $this->getEncoding() . "\r\n"; + if ($this->getContentType() == 'text/html' && false === strpos($headers, 'MIME-Version:')) { + $headers .= 'MIME-Version: 1.0' . "\r\n"; + } + + $subject = $this->subject; + if ($records) { + $subjectFormatter = new LineFormatter($this->subject); + $subject = $subjectFormatter->format($this->getHighestRecord($records)); + } + + $parameters = implode(' ', $this->parameters); + foreach ($this->to as $to) { + mail($to, $subject, $content, $headers, $parameters); + } + } + + /** + * @return string $contentType + */ + public function getContentType() + { + return $this->contentType; + } + + /** + * @return string $encoding + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML + * messages. + * @return self + */ + public function setContentType($contentType) + { + if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) { + throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection'); + } + + $this->contentType = $contentType; + + return $this; + } + + /** + * @param string $encoding + * @return self + */ + public function setEncoding($encoding) + { + if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) { + throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection'); + } + + $this->encoding = $encoding; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php new file mode 100644 index 00000000..6718e9e0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.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 Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; + +/** + * Class to record a log on a NewRelic application. + * Enabling New Relic High Security mode may prevent capture of useful information. + * + * @see https://docs.newrelic.com/docs/agents/php-agent + * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security + */ +class NewRelicHandler extends AbstractProcessingHandler +{ + /** + * Name of the New Relic application that will receive logs from this handler. + * + * @var string + */ + protected $appName; + + /** + * Name of the current transaction + * + * @var string + */ + protected $transactionName; + + /** + * Some context and extra data is passed into the handler as arrays of values. Do we send them as is + * (useful if we are using the API), or explode them for display on the NewRelic RPM website? + * + * @var bool + */ + protected $explodeArrays; + + /** + * {@inheritDoc} + * + * @param string $appName + * @param bool $explodeArrays + * @param string $transactionName + */ + public function __construct( + $level = Logger::ERROR, + $bubble = true, + $appName = null, + $explodeArrays = false, + $transactionName = null + ) { + parent::__construct($level, $bubble); + + $this->appName = $appName; + $this->explodeArrays = $explodeArrays; + $this->transactionName = $transactionName; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + if (!$this->isNewRelicEnabled()) { + throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); + } + + if ($appName = $this->getAppName($record['context'])) { + $this->setNewRelicAppName($appName); + } + + if ($transactionName = $this->getTransactionName($record['context'])) { + $this->setNewRelicTransactionName($transactionName); + unset($record['formatted']['context']['transaction_name']); + } + + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { + newrelic_notice_error($record['message'], $record['context']['exception']); + unset($record['formatted']['context']['exception']); + } else { + newrelic_notice_error($record['message']); + } + + if (isset($record['formatted']['context']) && is_array($record['formatted']['context'])) { + foreach ($record['formatted']['context'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('context_' . $key, $parameter); + } + } + } + + if (isset($record['formatted']['extra']) && is_array($record['formatted']['extra'])) { + foreach ($record['formatted']['extra'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('extra_' . $key, $parameter); + } + } + } + } + + /** + * Checks whether the NewRelic extension is enabled in the system. + * + * @return bool + */ + protected function isNewRelicEnabled() + { + return extension_loaded('newrelic'); + } + + /** + * Returns the appname where this log should be sent. Each log can override the default appname, set in this + * handler's constructor, by providing the appname in it's context. + * + * @param array $context + * @return null|string + */ + protected function getAppName(array $context) + { + if (isset($context['appname'])) { + return $context['appname']; + } + + return $this->appName; + } + + /** + * Returns the name of the current transaction. Each log can override the default transaction name, set in this + * handler's constructor, by providing the transaction_name in it's context + * + * @param array $context + * + * @return null|string + */ + protected function getTransactionName(array $context) + { + if (isset($context['transaction_name'])) { + return $context['transaction_name']; + } + + return $this->transactionName; + } + + /** + * Sets the NewRelic application that should receive this log. + * + * @param string $appName + */ + protected function setNewRelicAppName($appName) + { + newrelic_set_appname($appName); + } + + /** + * Overwrites the name of the current transaction + * + * @param string $transactionName + */ + protected function setNewRelicTransactionName($transactionName) + { + newrelic_name_transaction($transactionName); + } + + /** + * @param string $key + * @param mixed $value + */ + protected function setNewRelicParameter($key, $value) + { + if (null === $value || is_scalar($value)) { + newrelic_add_custom_parameter($key, $value); + } else { + newrelic_add_custom_parameter($key, @json_encode($value)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new NormalizerFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php new file mode 100644 index 00000000..4b845883 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.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 Monolog\Handler; + +use Monolog\Logger; + +/** + * Blackhole + * + * Any record it can handle will be thrown away. This can be used + * to put on top of an existing stack to override it temporarily. + * + * @author Jordi Boggiano + */ +class NullHandler extends AbstractHandler +{ + /** + * @param int $level The minimum logging level at which this handler will be triggered + */ + public function __construct($level = Logger::DEBUG) + { + parent::__construct($level, false); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + return true; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php new file mode 100644 index 00000000..1f2076a4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php @@ -0,0 +1,242 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Exception; +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; +use PhpConsole\Connector; +use PhpConsole\Handler; +use PhpConsole\Helper; + +/** + * Monolog handler for Google Chrome extension "PHP Console" + * + * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely + * + * Usage: + * 1. Install Google Chrome extension https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef + * 2. See overview https://github.com/barbushin/php-console#overview + * 3. Install PHP Console library https://github.com/barbushin/php-console#installation + * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png) + * + * $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler())); + * \Monolog\ErrorHandler::register($logger); + * echo $undefinedVar; + * $logger->addDebug('SELECT * FROM users', array('db', 'time' => 0.012)); + * PC::debug($_SERVER); // PHP Console debugger for any type of vars + * + * @author Sergey Barbushin https://www.linkedin.com/in/barbushin + */ +class PHPConsoleHandler extends AbstractProcessingHandler +{ + private $options = array( + 'enabled' => true, // bool Is PHP Console server enabled + 'classesPartialsTraceIgnore' => array('Monolog\\'), // array Hide calls of classes started with... + 'debugTagsKeysInContext' => array(0, 'tag'), // bool Is PHP Console server enabled + 'useOwnErrorsHandler' => false, // bool Enable errors handling + 'useOwnExceptionsHandler' => false, // bool Enable exceptions handling + 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths + 'registerHelper' => true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s') + 'serverEncoding' => null, // string|null Server internal encoding + 'headersLimit' => null, // int|null Set headers size limit for your web-server + 'password' => null, // string|null Protect PHP Console connection by password + 'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed + 'ipMasks' => array(), // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') + 'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) + 'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings + 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level + 'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number + 'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item + 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON + 'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug + 'dataStorage' => null, // PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) + ); + + /** @var Connector */ + private $connector; + + /** + * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details + * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) + * @param int $level + * @param bool $bubble + * @throws Exception + */ + public function __construct(array $options = array(), Connector $connector = null, $level = Logger::DEBUG, $bubble = true) + { + if (!class_exists('PhpConsole\Connector')) { + throw new Exception('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + } + parent::__construct($level, $bubble); + $this->options = $this->initOptions($options); + $this->connector = $this->initConnector($connector); + } + + private function initOptions(array $options) + { + $wrongOptions = array_diff(array_keys($options), array_keys($this->options)); + if ($wrongOptions) { + throw new Exception('Unknown options: ' . implode(', ', $wrongOptions)); + } + + return array_replace($this->options, $options); + } + + private function initConnector(Connector $connector = null) + { + if (!$connector) { + if ($this->options['dataStorage']) { + Connector::setPostponeStorage($this->options['dataStorage']); + } + $connector = Connector::getInstance(); + } + + if ($this->options['registerHelper'] && !Helper::isRegistered()) { + Helper::register(); + } + + if ($this->options['enabled'] && $connector->isActiveClient()) { + if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { + $handler = Handler::getInstance(); + $handler->setHandleErrors($this->options['useOwnErrorsHandler']); + $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); + $handler->start(); + } + if ($this->options['sourcesBasePath']) { + $connector->setSourcesBasePath($this->options['sourcesBasePath']); + } + if ($this->options['serverEncoding']) { + $connector->setServerEncoding($this->options['serverEncoding']); + } + if ($this->options['password']) { + $connector->setPassword($this->options['password']); + } + if ($this->options['enableSslOnlyMode']) { + $connector->enableSslOnlyMode(); + } + if ($this->options['ipMasks']) { + $connector->setAllowedIpMasks($this->options['ipMasks']); + } + if ($this->options['headersLimit']) { + $connector->setHeadersLimit($this->options['headersLimit']); + } + if ($this->options['detectDumpTraceAndSource']) { + $connector->getDebugDispatcher()->detectTraceAndSource = true; + } + $dumper = $connector->getDumper(); + $dumper->levelLimit = $this->options['dumperLevelLimit']; + $dumper->itemsCountLimit = $this->options['dumperItemsCountLimit']; + $dumper->itemSizeLimit = $this->options['dumperItemSizeLimit']; + $dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit']; + $dumper->detectCallbacks = $this->options['dumperDetectCallbacks']; + if ($this->options['enableEvalListener']) { + $connector->startEvalRequestsListener(); + } + } + + return $connector; + } + + public function getConnector() + { + return $this->connector; + } + + public function getOptions() + { + return $this->options; + } + + public function handle(array $record) + { + if ($this->options['enabled'] && $this->connector->isActiveClient()) { + return parent::handle($record); + } + + return !$this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * @return void + */ + protected function write(array $record) + { + if ($record['level'] < Logger::NOTICE) { + $this->handleDebugRecord($record); + } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof Exception) { + $this->handleExceptionRecord($record); + } else { + $this->handleErrorRecord($record); + } + } + + private function handleDebugRecord(array $record) + { + $tags = $this->getRecordTags($record); + $message = $record['message']; + if ($record['context']) { + $message .= ' ' . json_encode($this->connector->getDumper()->dump(array_filter($record['context']))); + } + $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']); + } + + private function handleExceptionRecord(array $record) + { + $this->connector->getErrorsDispatcher()->dispatchException($record['context']['exception']); + } + + private function handleErrorRecord(array $record) + { + $context = $record['context']; + + $this->connector->getErrorsDispatcher()->dispatchError( + isset($context['code']) ? $context['code'] : null, + isset($context['message']) ? $context['message'] : $record['message'], + isset($context['file']) ? $context['file'] : null, + isset($context['line']) ? $context['line'] : null, + $this->options['classesPartialsTraceIgnore'] + ); + } + + private function getRecordTags(array &$record) + { + $tags = null; + if (!empty($record['context'])) { + $context = & $record['context']; + foreach ($this->options['debugTagsKeysInContext'] as $key) { + if (!empty($context[$key])) { + $tags = $context[$key]; + if ($key === 0) { + array_shift($context); + } else { + unset($context[$key]); + } + break; + } + } + } + + return $tags ?: strtolower($record['level_name']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('%message%'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php new file mode 100644 index 00000000..1ae85845 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.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 Monolog\Handler; + +use Monolog\Logger; +use Psr\Log\LoggerInterface; + +/** + * Proxies log messages to an existing PSR-3 compliant logger. + * + * @author Michael Moussa + */ +class PsrHandler extends AbstractHandler +{ + /** + * PSR-3 compliant logger + * + * @var LoggerInterface + */ + protected $logger; + + /** + * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->logger = $logger; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + $this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']); + + return false === $this->bubble; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php new file mode 100644 index 00000000..bba72005 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.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 Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through the pushover api to mobile phones + * + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + */ +class PushoverHandler extends SocketHandler +{ + private $token; + private $users; + private $title; + private $user; + private $retry; + private $expire; + + private $highPriorityLevel; + private $emergencyLevel; + private $useFormattedMessage = false; + + /** + * All parameters that can be sent to Pushover + * @see https://pushover.net/api + * @var array + */ + private $parameterNames = array( + 'token' => true, + 'user' => true, + 'message' => true, + 'device' => true, + 'title' => true, + 'url' => true, + 'url_title' => true, + 'priority' => true, + 'timestamp' => true, + 'sound' => true, + 'retry' => true, + 'expire' => true, + 'callback' => true, + ); + + /** + * Sounds the api supports by default + * @see https://pushover.net/api#sounds + * @var array + */ + private $sounds = array( + 'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', + 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', + 'persistent', 'echo', 'updown', 'none', + ); + + /** + * @param string $token Pushover api token + * @param string|array $users Pushover user id or array of ids the message will be sent to + * @param string $title Title sent to the Pushover API + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $useSSL Whether to connect via SSL. Required when pushing messages to users that are not + * the pushover.net app owner. OpenSSL is required for this option. + * @param int $highPriorityLevel The minimum logging level at which this handler will start + * sending "high priority" requests to the Pushover API + * @param int $emergencyLevel The minimum logging level at which this handler will start + * sending "emergency" requests to the Pushover API + * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user. + * @param int $expire The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds). + */ + public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, $retry = 30, $expire = 25200) + { + $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; + parent::__construct($connectionString, $level, $bubble); + + $this->token = $token; + $this->users = (array) $users; + $this->title = $title ?: gethostname(); + $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel); + $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel); + $this->retry = $retry; + $this->expire = $expire; + } + + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + private function buildContent($record) + { + // Pushover has a limit of 512 characters on title and message combined. + $maxMessageLength = 512 - strlen($this->title); + + $message = ($this->useFormattedMessage) ? $record['formatted'] : $record['message']; + $message = substr($message, 0, $maxMessageLength); + + $timestamp = $record['datetime']->getTimestamp(); + + $dataArray = array( + 'token' => $this->token, + 'user' => $this->user, + 'message' => $message, + 'title' => $this->title, + 'timestamp' => $timestamp, + ); + + if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) { + $dataArray['priority'] = 2; + $dataArray['retry'] = $this->retry; + $dataArray['expire'] = $this->expire; + } elseif (isset($record['level']) && $record['level'] >= $this->highPriorityLevel) { + $dataArray['priority'] = 1; + } + + // First determine the available parameters + $context = array_intersect_key($record['context'], $this->parameterNames); + $extra = array_intersect_key($record['extra'], $this->parameterNames); + + // Least important info should be merged with subsequent info + $dataArray = array_merge($extra, $context, $dataArray); + + // Only pass sounds that are supported by the API + if (isset($dataArray['sound']) && !in_array($dataArray['sound'], $this->sounds)) { + unset($dataArray['sound']); + } + + return http_build_query($dataArray); + } + + private function buildHeader($content) + { + $header = "POST /1/messages.json HTTP/1.1\r\n"; + $header .= "Host: api.pushover.net\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + protected function write(array $record) + { + foreach ($this->users as $user) { + $this->user = $user; + + parent::write($record); + $this->closeSocket(); + } + + $this->user = null; + } + + public function setHighPriorityLevel($value) + { + $this->highPriorityLevel = $value; + } + + public function setEmergencyLevel($value) + { + $this->emergencyLevel = $value; + } + + /** + * Use the formatted message? + * @param bool $value + */ + public function useFormattedMessage($value) + { + $this->useFormattedMessage = (boolean) $value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php new file mode 100644 index 00000000..53a8b391 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Raven_Client; + +/** + * Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server + * using raven-php (https://github.com/getsentry/raven-php) + * + * @author Marc Abramowitz + */ +class RavenHandler extends AbstractProcessingHandler +{ + /** + * Translates Monolog log levels to Raven log levels. + */ + private $logLevels = array( + Logger::DEBUG => Raven_Client::DEBUG, + Logger::INFO => Raven_Client::INFO, + Logger::NOTICE => Raven_Client::INFO, + Logger::WARNING => Raven_Client::WARNING, + Logger::ERROR => Raven_Client::ERROR, + Logger::CRITICAL => Raven_Client::FATAL, + Logger::ALERT => Raven_Client::FATAL, + Logger::EMERGENCY => Raven_Client::FATAL, + ); + + /** + * @var string should represent the current version of the calling + * software. Can be any string (git commit, version number) + */ + private $release; + + /** + * @var Raven_Client the client object that sends the message to the server + */ + protected $ravenClient; + + /** + * @var LineFormatter The formatter to use for the logs generated via handleBatch() + */ + protected $batchFormatter; + + /** + * @param Raven_Client $ravenClient + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->ravenClient = $ravenClient; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $level = $this->level; + + // filter records based on their level + $records = array_filter($records, function ($record) use ($level) { + return $record['level'] >= $level; + }); + + if (!$records) { + return; + } + + // the record with the highest severity is the "main" one + $record = array_reduce($records, function ($highest, $record) { + if ($record['level'] > $highest['level']) { + return $record; + } + + return $highest; + }); + + // the other ones are added as a context item + $logs = array(); + foreach ($records as $r) { + $logs[] = $this->processRecord($r); + } + + if ($logs) { + $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs); + } + + $this->handle($record); + } + + /** + * Sets the formatter for the logs generated by handleBatch(). + * + * @param FormatterInterface $formatter + */ + public function setBatchFormatter(FormatterInterface $formatter) + { + $this->batchFormatter = $formatter; + } + + /** + * Gets the formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + public function getBatchFormatter() + { + if (!$this->batchFormatter) { + $this->batchFormatter = $this->getDefaultBatchFormatter(); + } + + return $this->batchFormatter; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $previousUserContext = false; + $options = array(); + $options['level'] = $this->logLevels[$record['level']]; + $options['tags'] = array(); + if (!empty($record['extra']['tags'])) { + $options['tags'] = array_merge($options['tags'], $record['extra']['tags']); + unset($record['extra']['tags']); + } + if (!empty($record['context']['tags'])) { + $options['tags'] = array_merge($options['tags'], $record['context']['tags']); + unset($record['context']['tags']); + } + if (!empty($record['context']['fingerprint'])) { + $options['fingerprint'] = $record['context']['fingerprint']; + unset($record['context']['fingerprint']); + } + if (!empty($record['context']['logger'])) { + $options['logger'] = $record['context']['logger']; + unset($record['context']['logger']); + } else { + $options['logger'] = $record['channel']; + } + foreach ($this->getExtraParameters() as $key) { + foreach (array('extra', 'context') as $source) { + if (!empty($record[$source][$key])) { + $options[$key] = $record[$source][$key]; + unset($record[$source][$key]); + } + } + } + if (!empty($record['context'])) { + $options['extra']['context'] = $record['context']; + if (!empty($record['context']['user'])) { + $previousUserContext = $this->ravenClient->context->user; + $this->ravenClient->user_context($record['context']['user']); + unset($options['extra']['context']['user']); + } + } + if (!empty($record['extra'])) { + $options['extra']['extra'] = $record['extra']; + } + + if (!empty($this->release) && !isset($options['release'])) { + $options['release'] = $this->release; + } + + if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { + $options['extra']['message'] = $record['formatted']; + $this->ravenClient->captureException($record['context']['exception'], $options); + } else { + $this->ravenClient->captureMessage($record['formatted'], array(), $options); + } + + if ($previousUserContext !== false) { + $this->ravenClient->user_context($previousUserContext); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[%channel%] %message%'); + } + + /** + * Gets the default formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + protected function getDefaultBatchFormatter() + { + return new LineFormatter(); + } + + /** + * Gets extra parameters supported by Raven that can be found in "extra" and "context" + * + * @return array + */ + protected function getExtraParameters() + { + return array('checksum', 'release', 'event_id'); + } + + /** + * @param string $value + * @return self + */ + public function setRelease($value) + { + $this->release = $value; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php new file mode 100644 index 00000000..590f9965 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.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 Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Logs to a Redis key using rpush + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod"); + * $log->pushHandler($redis); + * + * @author Thomas Tourlourat + */ +class RedisHandler extends AbstractProcessingHandler +{ + private $redisClient; + private $redisKey; + protected $capSize; + + /** + * @param \Predis\Client|\Redis $redis The redis instance + * @param string $key The key name to push records to + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $capSize Number of entries to limit list size to + */ + public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true, $capSize = false) + { + if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { + throw new \InvalidArgumentException('Predis\Client or Redis instance required'); + } + + $this->redisClient = $redis; + $this->redisKey = $key; + $this->capSize = $capSize; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + if ($this->capSize) { + $this->writeCapped($record); + } else { + $this->redisClient->rpush($this->redisKey, $record["formatted"]); + } + } + + /** + * Write and cap the collection + * Writes the record to the redis list and caps its + * + * @param array $record associative record array + * @return void + */ + protected function writeCapped(array $record) + { + if ($this->redisClient instanceof \Redis) { + $this->redisClient->multi() + ->rpush($this->redisKey, $record["formatted"]) + ->ltrim($this->redisKey, -$this->capSize, -1) + ->exec(); + } else { + $redisKey = $this->redisKey; + $capSize = $this->capSize; + $this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) { + $tx->rpush($redisKey, $record["formatted"]); + $tx->ltrim($redisKey, -$capSize, -1); + }); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php new file mode 100644 index 00000000..0d9de1a2 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.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 Monolog\Handler; + +use RollbarNotifier; +use Exception; +use Monolog\Logger; + +/** + * Sends errors to Rollbar + * + * If the context data contains a `payload` key, that is used as an array + * of payload options to RollbarNotifier's report_message/report_exception methods. + * + * Rollbar's context info will contain the context + extra keys from the log record + * merged, and then on top of that a few keys: + * + * - level (rollbar level name) + * - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8) + * - channel + * - datetime (unix timestamp) + * + * @author Paul Statezny + */ +class RollbarHandler extends AbstractProcessingHandler +{ + /** + * Rollbar notifier + * + * @var RollbarNotifier + */ + protected $rollbarNotifier; + + protected $levelMap = array( + Logger::DEBUG => 'debug', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warning', + Logger::ERROR => 'error', + Logger::CRITICAL => 'critical', + Logger::ALERT => 'critical', + Logger::EMERGENCY => 'critical', + ); + + /** + * Records whether any log records have been added since the last flush of the rollbar notifier + * + * @var bool + */ + private $hasRecords = false; + + protected $initialized = false; + + /** + * @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(RollbarNotifier $rollbarNotifier, $level = Logger::ERROR, $bubble = true) + { + $this->rollbarNotifier = $rollbarNotifier; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function(array($this, 'close')); + $this->initialized = true; + } + + $context = $record['context']; + $payload = array(); + if (isset($context['payload'])) { + $payload = $context['payload']; + unset($context['payload']); + } + $context = array_merge($context, $record['extra'], array( + 'level' => $this->levelMap[$record['level']], + 'monolog_level' => $record['level_name'], + 'channel' => $record['channel'], + 'datetime' => $record['datetime']->format('U'), + )); + + if (isset($context['exception']) && $context['exception'] instanceof Exception) { + $exception = $context['exception']; + unset($context['exception']); + + $this->rollbarNotifier->report_exception($exception, $context, $payload); + } else { + $this->rollbarNotifier->report_message( + $record['message'], + $context['level'], + $context, + $payload + ); + } + + $this->hasRecords = true; + } + + public function flush() + { + if ($this->hasRecords) { + $this->rollbarNotifier->flush(); + $this->hasRecords = false; + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->flush(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php new file mode 100644 index 00000000..3b60b3d1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.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 Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores logs to files that are rotated every day and a limited number of files are kept. + * + * This rotation is only intended to be used as a workaround. Using logrotate to + * handle the rotation is strongly encouraged when you can use it. + * + * @author Christophe Coevoet + * @author Jordi Boggiano + */ +class RotatingFileHandler extends StreamHandler +{ + const FILE_PER_DAY = 'Y-m-d'; + const FILE_PER_MONTH = 'Y-m'; + const FILE_PER_YEAR = 'Y'; + + protected $filename; + protected $maxFiles; + protected $mustRotate; + protected $nextRotation; + protected $filenameFormat; + protected $dateFormat; + + /** + * @param string $filename + * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param Boolean $useLocking Try to lock log file before doing any writes + */ + public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + { + $this->filename = $filename; + $this->maxFiles = (int) $maxFiles; + $this->nextRotation = new \DateTime('tomorrow'); + $this->filenameFormat = '{filename}-{date}'; + $this->dateFormat = 'Y-m-d'; + + parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + public function setFilenameFormat($filenameFormat, $dateFormat) + { + if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { + trigger_error( + 'Invalid date format - format must be one of '. + 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '. + 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '. + 'date formats using slashes, underscores and/or dots instead of dashes.', + E_USER_DEPRECATED + ); + } + if (substr_count($filenameFormat, '{date}') === 0) { + trigger_error( + 'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.', + E_USER_DEPRECATED + ); + } + $this->filenameFormat = $filenameFormat; + $this->dateFormat = $dateFormat; + $this->url = $this->getTimedFilename(); + $this->close(); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + // on the first record written, if the log is new, we should rotate (once per day) + if (null === $this->mustRotate) { + $this->mustRotate = !file_exists($this->url); + } + + if ($this->nextRotation < $record['datetime']) { + $this->mustRotate = true; + $this->close(); + } + + parent::write($record); + } + + /** + * Rotates the files. + */ + protected function rotate() + { + // update filename + $this->url = $this->getTimedFilename(); + $this->nextRotation = new \DateTime('tomorrow'); + + // skip GC of old logs if files are unlimited + if (0 === $this->maxFiles) { + return; + } + + $logFiles = glob($this->getGlobPattern()); + if ($this->maxFiles >= count($logFiles)) { + // no files to remove + return; + } + + // Sorting the files by name to remove the older ones + usort($logFiles, function ($a, $b) { + return strcmp($b, $a); + }); + + foreach (array_slice($logFiles, $this->maxFiles) as $file) { + if (is_writable($file)) { + // suppress errors here as unlink() might fail if two processes + // are cleaning up/rotating at the same time + set_error_handler(function ($errno, $errstr, $errfile, $errline) {}); + unlink($file); + restore_error_handler(); + } + } + + $this->mustRotate = false; + } + + protected function getTimedFilename() + { + $fileInfo = pathinfo($this->filename); + $timedFilename = str_replace( + array('{filename}', '{date}'), + array($fileInfo['filename'], date($this->dateFormat)), + $fileInfo['dirname'] . '/' . $this->filenameFormat + ); + + if (!empty($fileInfo['extension'])) { + $timedFilename .= '.'.$fileInfo['extension']; + } + + return $timedFilename; + } + + protected function getGlobPattern() + { + $fileInfo = pathinfo($this->filename); + $glob = str_replace( + array('{filename}', '{date}'), + array($fileInfo['filename'], '*'), + $fileInfo['dirname'] . '/' . $this->filenameFormat + ); + if (!empty($fileInfo['extension'])) { + $glob .= '.'.$fileInfo['extension']; + } + + return $glob; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php new file mode 100644 index 00000000..9509ae37 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.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 Monolog\Handler; + +/** + * Sampling handler + * + * A sampled event stream can be useful for logging high frequency events in + * a production environment where you only need an idea of what is happening + * and are not concerned with capturing every occurrence. Since the decision to + * handle or not handle a particular event is determined randomly, the + * resulting sampled log is not guaranteed to contain 1/N of the events that + * occurred in the application, but based on the Law of large numbers, it will + * tend to be close to this ratio with a large number of attempts. + * + * @author Bryan Davis + * @author Kunal Mehta + */ +class SamplingHandler extends AbstractHandler +{ + /** + * @var callable|HandlerInterface $handler + */ + protected $handler; + + /** + * @var int $factor + */ + protected $factor; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler). + * @param int $factor Sample factor + */ + public function __construct($handler, $factor) + { + parent::__construct(); + $this->handler = $handler; + $this->factor = $factor; + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + public function isHandling(array $record) + { + return $this->handler->isHandling($record); + } + + public function handle(array $record) + { + if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) { + // The same logic as in FingersCrossedHandler + if (!$this->handler instanceof HandlerInterface) { + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->handler->handle($record); + } + + return false === $this->bubble; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php new file mode 100644 index 00000000..38bc838a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.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 Monolog\Handler\Slack; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Slack record utility helping to log to Slack webhooks or API. + * + * @author Greg Kedzierski + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + * @see https://api.slack.com/docs/message-attachments + */ +class SlackRecord +{ + const COLOR_DANGER = 'danger'; + + const COLOR_WARNING = 'warning'; + + const COLOR_GOOD = 'good'; + + const COLOR_DEFAULT = '#e3e4e6'; + + /** + * Slack channel (encoded ID or name) + * @var string|null + */ + private $channel; + + /** + * Name of a bot + * @var string|null + */ + private $username; + + /** + * User icon e.g. 'ghost', 'http://example.com/user.png' + * @var string + */ + private $userIcon; + + /** + * Whether the message should be added to Slack as attachment (plain text otherwise) + * @var bool + */ + private $useAttachment; + + /** + * Whether the the context/extra messages added to Slack as attachments are in a short style + * @var bool + */ + private $useShortAttachment; + + /** + * Whether the attachment should include context and extra data + * @var bool + */ + private $includeContextAndExtra; + + /** + * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @var array + */ + private $excludeFields; + + /** + * @var FormatterInterface + */ + private $formatter; + + /** + * @var NormalizerFormatter + */ + private $normalizerFormatter; + + public function __construct($channel = null, $username = null, $useAttachment = true, $userIcon = null, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array(), FormatterInterface $formatter = null) + { + $this->channel = $channel; + $this->username = $username; + $this->userIcon = trim($userIcon, ':'); + $this->useAttachment = $useAttachment; + $this->useShortAttachment = $useShortAttachment; + $this->includeContextAndExtra = $includeContextAndExtra; + $this->excludeFields = $excludeFields; + $this->formatter = $formatter; + + if ($this->includeContextAndExtra) { + $this->normalizerFormatter = new NormalizerFormatter(); + } + } + + public function getSlackData(array $record) + { + $dataArray = array(); + $record = $this->excludeFields($record); + + if ($this->username) { + $dataArray['username'] = $this->username; + } + + if ($this->channel) { + $dataArray['channel'] = $this->channel; + } + + if ($this->formatter && !$this->useAttachment) { + $message = $this->formatter->format($record); + } else { + $message = $record['message']; + } + + if ($this->useAttachment) { + $attachment = array( + 'fallback' => $message, + 'text' => $message, + 'color' => $this->getAttachmentColor($record['level']), + 'fields' => array(), + 'mrkdwn_in' => array('fields'), + 'ts' => $record['datetime']->getTimestamp() + ); + + if ($this->useShortAttachment) { + $attachment['title'] = $record['level_name']; + } else { + $attachment['title'] = 'Message'; + $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name']); + } + + + if ($this->includeContextAndExtra) { + foreach (array('extra', 'context') as $key) { + if (empty($record[$key])) { + continue; + } + + if ($this->useShortAttachment) { + $attachment['fields'][] = $this->generateAttachmentField( + ucfirst($key), + $record[$key] + ); + } else { + // Add all extra fields as individual fields in attachment + $attachment['fields'] = array_merge( + $attachment['fields'], + $this->generateAttachmentFields($record[$key]) + ); + } + } + } + + $dataArray['attachments'] = array($attachment); + } else { + $dataArray['text'] = $message; + } + + if ($this->userIcon) { + if (filter_var($this->userIcon, FILTER_VALIDATE_URL)) { + $dataArray['icon_url'] = $this->userIcon; + } else { + $dataArray['icon_emoji'] = ":{$this->userIcon}:"; + } + } + + return $dataArray; + } + + /** + * Returned a Slack message attachment color associated with + * provided level. + * + * @param int $level + * @return string + */ + public function getAttachmentColor($level) + { + switch (true) { + case $level >= Logger::ERROR: + return self::COLOR_DANGER; + case $level >= Logger::WARNING: + return self::COLOR_WARNING; + case $level >= Logger::INFO: + return self::COLOR_GOOD; + default: + return self::COLOR_DEFAULT; + } + } + + /** + * Stringifies an array of key/value pairs to be used in attachment fields + * + * @param array $fields + * + * @return string + */ + public function stringify($fields) + { + $normalized = $this->normalizerFormatter->format($fields); + $prettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128; + + $hasSecondDimension = count(array_filter($normalized, 'is_array')); + $hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric')); + + return $hasSecondDimension || $hasNonNumericKeys + ? json_encode($normalized, $prettyPrintFlag) + : json_encode($normalized); + } + + /** + * Sets the formatter + * + * @param FormatterInterface $formatter + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + } + + /** + * Generates attachment field + * + * @param string $title + * @param string|array $value\ + * + * @return array + */ + private function generateAttachmentField($title, $value) + { + $value = is_array($value) + ? sprintf('```%s```', $this->stringify($value)) + : $value; + + return array( + 'title' => $title, + 'value' => $value, + 'short' => false + ); + } + + /** + * Generates a collection of attachment fields from array + * + * @param array $data + * + * @return array + */ + private function generateAttachmentFields(array $data) + { + $fields = array(); + foreach ($data as $key => $value) { + $fields[] = $this->generateAttachmentField($key, $value); + } + + return $fields; + } + + /** + * Get a copy of record with fields excluded according to $this->excludeFields + * + * @param array $record + * + * @return array + */ + private function excludeFields(array $record) + { + foreach ($this->excludeFields as $field) { + $keys = explode('.', $field); + $node = &$record; + $lastKey = end($keys); + foreach ($keys as $key) { + if (!isset($node[$key])) { + break; + } + if ($lastKey === $key) { + unset($node[$key]); + break; + } + $node = &$node[$key]; + } + } + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php new file mode 100644 index 00000000..9324fc9a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Handler\Slack\SlackRecord; + +/** + * Sends notifications through Slack API + * + * @author Greg Kedzierski + * @see https://api.slack.com/ + */ +class SlackHandler extends SocketHandler +{ + /** + * Slack API token + * @var string + */ + private $token; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord + */ + private $slackRecord; + + /** + * @param string $token Slack API token + * @param string $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param array $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @throws MissingExtensionException If no OpenSSL PHP extension configured + */ + public function __construct($token, $channel, $username = null, $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array()) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); + } + + parent::__construct('ssl://slack.com:443', $level, $bubble); + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields, + $this->formatter + ); + + $this->token = $token; + } + + public function getSlackRecord() + { + return $this->slackRecord; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = $this->prepareContentData($record); + + return http_build_query($dataArray); + } + + /** + * Prepares content data + * + * @param array $record + * @return array + */ + protected function prepareContentData($record) + { + $dataArray = $this->slackRecord->getSlackData($record); + $dataArray['token'] = $this->token; + + if (!empty($dataArray['attachments'])) { + $dataArray['attachments'] = json_encode($dataArray['attachments']); + } + + return $dataArray; + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST /api/chat.postMessage HTTP/1.1\r\n"; + $header .= "Host: slack.com\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + parent::write($record); + $res = $this->getResource(); + if (is_resource($res)) { + @fread($res, 2048); + } + $this->closeSocket(); + } + + /** + * Returned a Slack message attachment color associated with + * provided level. + * + * @param int $level + * @return string + * @deprecated Use underlying SlackRecord instead + */ + protected function getAttachmentColor($level) + { + trigger_error( + 'SlackHandler::getAttachmentColor() is deprecated. Use underlying SlackRecord instead.', + E_USER_DEPRECATED + ); + + return $this->slackRecord->getAttachmentColor($level); + } + + /** + * Stringifies an array of key/value pairs to be used in attachment fields + * + * @param array $fields + * @return string + * @deprecated Use underlying SlackRecord instead + */ + protected function stringify($fields) + { + trigger_error( + 'SlackHandler::stringify() is deprecated. Use underlying SlackRecord instead.', + E_USER_DEPRECATED + ); + + return $this->slackRecord->stringify($fields); + } + + public function setFormatter(FormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter() + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php new file mode 100644 index 00000000..0d3e3ea1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.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 Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Handler\Slack\SlackRecord; + +/** + * Sends notifications through Slack Webhooks + * + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + */ +class SlackWebhookHandler extends AbstractProcessingHandler +{ + /** + * Slack Webhook token + * @var string + */ + private $webhookUrl; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord + */ + private $slackRecord; + + /** + * @param string $webhookUrl Slack Webhook URL + * @param string|null $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param array $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + */ + public function __construct($webhookUrl, $channel = null, $username = null, $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeContextAndExtra = false, $level = Logger::CRITICAL, $bubble = true, array $excludeFields = array()) + { + parent::__construct($level, $bubble); + + $this->webhookUrl = $webhookUrl; + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields, + $this->formatter + ); + } + + public function getSlackRecord() + { + return $this->slackRecord; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + $postData = $this->slackRecord->getSlackData($record); + $postString = json_encode($postData); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $this->webhookUrl); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + if (defined('CURLOPT_SAFE_UPLOAD')) { + curl_setopt($ch, CURLOPT_SAFE_UPLOAD, true); + } + curl_setopt($ch, CURLOPT_POSTFIELDS, array('payload' => $postString)); + + Curl\Util::execute($ch); + } + + public function setFormatter(FormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter() + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php new file mode 100644 index 00000000..baead525 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.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 Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through Slack's Slackbot + * + * @author Haralan Dobrev + * @see https://slack.com/apps/A0F81R8ET-slackbot + */ +class SlackbotHandler extends AbstractProcessingHandler +{ + /** + * The slug of the Slack team + * @var string + */ + private $slackTeam; + + /** + * Slackbot token + * @var string + */ + private $token; + + /** + * Slack channel name + * @var string + */ + private $channel; + + /** + * @param string $slackTeam Slack team slug + * @param string $token Slackbot token + * @param string $channel Slack channel (encoded ID or name) + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($slackTeam, $token, $channel, $level = Logger::CRITICAL, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->slackTeam = $slackTeam; + $this->token = $token; + $this->channel = $channel; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + $slackbotUrl = sprintf( + 'https://%s.slack.com/services/hooks/slackbot?token=%s&channel=%s', + $this->slackTeam, + $this->token, + $this->channel + ); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $slackbotUrl); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $record['message']); + + Curl\Util::execute($ch); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php new file mode 100644 index 00000000..7a61bf4e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php @@ -0,0 +1,346 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to any socket - uses fsockopen() or pfsockopen(). + * + * @author Pablo de Leon Belloc + * @see http://php.net/manual/en/function.fsockopen.php + */ +class SocketHandler extends AbstractProcessingHandler +{ + private $connectionString; + private $connectionTimeout; + private $resource; + private $timeout = 0; + private $writingTimeout = 10; + private $lastSentBytes = null; + private $persistent = false; + private $errno; + private $errstr; + private $lastWritingAt; + + /** + * @param string $connectionString Socket connection string + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + $this->connectionString = $connectionString; + $this->connectionTimeout = (float) ini_get('default_socket_timeout'); + } + + /** + * Connect (if necessary) and write to the socket + * + * @param array $record + * + * @throws \UnexpectedValueException + * @throws \RuntimeException + */ + protected function write(array $record) + { + $this->connectIfNotConnected(); + $data = $this->generateDataStream($record); + $this->writeToSocket($data); + } + + /** + * We will not close a PersistentSocket instance so it can be reused in other requests. + */ + public function close() + { + if (!$this->isPersistent()) { + $this->closeSocket(); + } + } + + /** + * Close socket, if open + */ + public function closeSocket() + { + if (is_resource($this->resource)) { + fclose($this->resource); + $this->resource = null; + } + } + + /** + * Set socket connection to nbe persistent. It only has effect before the connection is initiated. + * + * @param bool $persistent + */ + public function setPersistent($persistent) + { + $this->persistent = (boolean) $persistent; + } + + /** + * Set connection timeout. Only has effect before we connect. + * + * @param float $seconds + * + * @see http://php.net/manual/en/function.fsockopen.php + */ + public function setConnectionTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->connectionTimeout = (float) $seconds; + } + + /** + * Set write timeout. Only has effect before we connect. + * + * @param float $seconds + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + public function setTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->timeout = (float) $seconds; + } + + /** + * Set writing timeout. Only has effect during connection in the writing cycle. + * + * @param float $seconds 0 for no timeout + */ + public function setWritingTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->writingTimeout = (float) $seconds; + } + + /** + * Get current connection string + * + * @return string + */ + public function getConnectionString() + { + return $this->connectionString; + } + + /** + * Get persistent setting + * + * @return bool + */ + public function isPersistent() + { + return $this->persistent; + } + + /** + * Get current connection timeout setting + * + * @return float + */ + public function getConnectionTimeout() + { + return $this->connectionTimeout; + } + + /** + * Get current in-transfer timeout + * + * @return float + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Get current local writing timeout + * + * @return float + */ + public function getWritingTimeout() + { + return $this->writingTimeout; + } + + /** + * Check to see if the socket is currently available. + * + * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. + * + * @return bool + */ + public function isConnected() + { + return is_resource($this->resource) + && !feof($this->resource); // on TCP - other party can close connection. + } + + /** + * Wrapper to allow mocking + */ + protected function pfsockopen() + { + return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + */ + protected function fsockopen() + { + return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + protected function streamSetTimeout() + { + $seconds = floor($this->timeout); + $microseconds = round(($this->timeout - $seconds) * 1e6); + + return stream_set_timeout($this->resource, $seconds, $microseconds); + } + + /** + * Wrapper to allow mocking + */ + protected function fwrite($data) + { + return @fwrite($this->resource, $data); + } + + /** + * Wrapper to allow mocking + */ + protected function streamGetMetadata() + { + return stream_get_meta_data($this->resource); + } + + private function validateTimeout($value) + { + $ok = filter_var($value, FILTER_VALIDATE_FLOAT); + if ($ok === false || $value < 0) { + throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); + } + } + + private function connectIfNotConnected() + { + if ($this->isConnected()) { + return; + } + $this->connect(); + } + + protected function generateDataStream($record) + { + return (string) $record['formatted']; + } + + /** + * @return resource|null + */ + protected function getResource() + { + return $this->resource; + } + + private function connect() + { + $this->createSocketResource(); + $this->setSocketTimeout(); + } + + private function createSocketResource() + { + if ($this->isPersistent()) { + $resource = $this->pfsockopen(); + } else { + $resource = $this->fsockopen(); + } + if (!$resource) { + throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); + } + $this->resource = $resource; + } + + private function setSocketTimeout() + { + if (!$this->streamSetTimeout()) { + throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); + } + } + + private function writeToSocket($data) + { + $length = strlen($data); + $sent = 0; + $this->lastSentBytes = $sent; + while ($this->isConnected() && $sent < $length) { + if (0 == $sent) { + $chunk = $this->fwrite($data); + } else { + $chunk = $this->fwrite(substr($data, $sent)); + } + if ($chunk === false) { + throw new \RuntimeException("Could not write to socket"); + } + $sent += $chunk; + $socketInfo = $this->streamGetMetadata(); + if ($socketInfo['timed_out']) { + throw new \RuntimeException("Write timed-out"); + } + + if ($this->writingIsTimedOut($sent)) { + throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)"); + } + } + if (!$this->isConnected() && $sent < $length) { + throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)"); + } + } + + private function writingIsTimedOut($sent) + { + $writingTimeout = (int) floor($this->writingTimeout); + if (0 === $writingTimeout) { + return false; + } + + if ($sent !== $this->lastSentBytes) { + $this->lastWritingAt = time(); + $this->lastSentBytes = $sent; + + return false; + } else { + usleep(100); + } + + if ((time() - $this->lastWritingAt) >= $writingTimeout) { + $this->closeSocket(); + + return true; + } + + return false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php new file mode 100644 index 00000000..09a15738 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to any stream resource + * + * Can be used to store into php://stderr, remote and local files, etc. + * + * @author Jordi Boggiano + */ +class StreamHandler extends AbstractProcessingHandler +{ + protected $stream; + protected $url; + private $errorMessage; + protected $filePermission; + protected $useLocking; + private $dirCreated; + + /** + * @param resource|string $stream + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param Boolean $useLocking Try to lock log file before doing any writes + * + * @throws \Exception If a missing directory is not buildable + * @throws \InvalidArgumentException If stream is not a resource or string + */ + public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + { + parent::__construct($level, $bubble); + if (is_resource($stream)) { + $this->stream = $stream; + } elseif (is_string($stream)) { + $this->url = $stream; + } else { + throw new \InvalidArgumentException('A stream must either be a resource or a string.'); + } + + $this->filePermission = $filePermission; + $this->useLocking = $useLocking; + } + + /** + * {@inheritdoc} + */ + public function close() + { + if ($this->url && is_resource($this->stream)) { + fclose($this->stream); + } + $this->stream = null; + } + + /** + * Return the currently active stream if it is open + * + * @return resource|null + */ + public function getStream() + { + return $this->stream; + } + + /** + * Return the stream URL if it was configured with a URL and not an active resource + * + * @return string|null + */ + public function getUrl() + { + return $this->url; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!is_resource($this->stream)) { + if (null === $this->url || '' === $this->url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); + } + $this->createDir(); + $this->errorMessage = null; + set_error_handler(array($this, 'customErrorHandler')); + $this->stream = fopen($this->url, 'a'); + if ($this->filePermission !== null) { + @chmod($this->url, $this->filePermission); + } + restore_error_handler(); + if (!is_resource($this->stream)) { + $this->stream = null; + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: '.$this->errorMessage, $this->url)); + } + } + + if ($this->useLocking) { + // ignoring errors here, there's not much we can do about them + flock($this->stream, LOCK_EX); + } + + $this->streamWrite($this->stream, $record); + + if ($this->useLocking) { + flock($this->stream, LOCK_UN); + } + } + + /** + * Write to stream + * @param resource $stream + * @param array $record + */ + protected function streamWrite($stream, array $record) + { + fwrite($stream, (string) $record['formatted']); + } + + private function customErrorHandler($code, $msg) + { + $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); + } + + /** + * @param string $stream + * + * @return null|string + */ + private function getDirFromStream($stream) + { + $pos = strpos($stream, '://'); + if ($pos === false) { + return dirname($stream); + } + + if ('file://' === substr($stream, 0, 7)) { + return dirname(substr($stream, 7)); + } + + return; + } + + private function createDir() + { + // Do not try to create dir if it has already been tried. + if ($this->dirCreated) { + return; + } + + $dir = $this->getDirFromStream($this->url); + if (null !== $dir && !is_dir($dir)) { + $this->errorMessage = null; + set_error_handler(array($this, 'customErrorHandler')); + $status = mkdir($dir, 0777, true); + restore_error_handler(); + if (false === $status) { + throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $dir)); + } + } + $this->dirCreated = true; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php new file mode 100644 index 00000000..aba13967 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.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 Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * SwiftMailerHandler uses Swift_Mailer to send the emails + * + * @author Gyula Sallai + */ +class SwiftMailerHandler extends MailHandler +{ + protected $mailer; + private $messageTemplate; + + /** + * @param \Swift_Mailer $mailer The mailer to use + * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->mailer = $mailer; + $this->messageTemplate = $message; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $this->mailer->send($this->buildMessage($content, $records)); + } + + /** + * Creates instance of Swift_Message to be sent + * + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + * @return \Swift_Message + */ + protected function buildMessage($content, array $records) + { + $message = null; + if ($this->messageTemplate instanceof \Swift_Message) { + $message = clone $this->messageTemplate; + $message->generateId(); + } elseif (is_callable($this->messageTemplate)) { + $message = call_user_func($this->messageTemplate, $content, $records); + } + + if (!$message instanceof \Swift_Message) { + throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it'); + } + + if ($records) { + $subjectFormatter = new LineFormatter($message->getSubject()); + $message->setSubject($subjectFormatter->format($this->getHighestRecord($records))); + } + + $message->setBody($content); + $message->setDate(time()); + + return $message; + } + + /** + * BC getter, to be removed in 2.0 + */ + public function __get($name) + { + if ($name === 'message') { + trigger_error('SwiftMailerHandler->message is deprecated, use ->buildMessage() instead to retrieve the message', E_USER_DEPRECATED); + + return $this->buildMessage(null, array()); + } + + throw new \InvalidArgumentException('Invalid property '.$name); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php new file mode 100644 index 00000000..376bc3b2 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.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 Monolog\Handler; + +use Monolog\Logger; + +/** + * Logs to syslog service. + * + * usage example: + * + * $log = new Logger('application'); + * $syslog = new SyslogHandler('myfacility', 'local6'); + * $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%"); + * $syslog->setFormatter($formatter); + * $log->pushHandler($syslog); + * + * @author Sven Paulus + */ +class SyslogHandler extends AbstractSyslogHandler +{ + protected $ident; + protected $logopts; + + /** + * @param string $ident + * @param mixed $facility + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID + */ + public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID) + { + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + $this->logopts = $logopts; + } + + /** + * {@inheritdoc} + */ + public function close() + { + closelog(); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!openlog($this->ident, $this->logopts, $this->facility)) { + throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"'); + } + syslog($this->logLevels[$record['level']], (string) $record['formatted']); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php new file mode 100644 index 00000000..3bff085b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.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 Monolog\Handler\SyslogUdp; + +class UdpSocket +{ + const DATAGRAM_MAX_LENGTH = 65023; + + protected $ip; + protected $port; + protected $socket; + + public function __construct($ip, $port = 514) + { + $this->ip = $ip; + $this->port = $port; + $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + } + + public function write($line, $header = "") + { + $this->send($this->assembleMessage($line, $header)); + } + + public function close() + { + if (is_resource($this->socket)) { + socket_close($this->socket); + $this->socket = null; + } + } + + protected function send($chunk) + { + if (!is_resource($this->socket)) { + throw new \LogicException('The UdpSocket to '.$this->ip.':'.$this->port.' has been closed and can not be written to anymore'); + } + socket_sendto($this->socket, $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); + } + + protected function assembleMessage($line, $header) + { + $chunkSize = self::DATAGRAM_MAX_LENGTH - strlen($header); + + return $header . substr($line, 0, $chunkSize); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php new file mode 100644 index 00000000..74d946a5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.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 Monolog\Handler; + +use Monolog\Logger; +use Monolog\Handler\SyslogUdp\UdpSocket; + +/** + * A Handler for logging to a remote syslogd server. + * + * @author Jesper Skovgaard Nielsen + */ +class SyslogUdpHandler extends AbstractSyslogHandler +{ + protected $socket; + + /** + * @param string $host + * @param int $port + * @param mixed $facility + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($host, $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($facility, $level, $bubble); + + $this->socket = new UdpSocket($host, $port ?: 514); + } + + protected function write(array $record) + { + $lines = $this->splitMessageIntoLines($record['formatted']); + + $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']]); + + foreach ($lines as $line) { + $this->socket->write($line, $header); + } + } + + public function close() + { + $this->socket->close(); + } + + private function splitMessageIntoLines($message) + { + if (is_array($message)) { + $message = implode("\n", $message); + } + + return preg_split('/$\R?^/m', $message, -1, PREG_SPLIT_NO_EMPTY); + } + + /** + * Make common syslog header (see rfc5424) + */ + protected function makeCommonSyslogHeader($severity) + { + $priority = $severity + $this->facility; + + return "<$priority>1 "; + } + + /** + * Inject your own socket, mainly used for testing + */ + public function setSocket($socket) + { + $this->socket = $socket; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php new file mode 100644 index 00000000..e39cfc66 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.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 Monolog\Handler; + +/** + * Used for testing purposes. + * + * It records all records and gives you access to them for verification. + * + * @author Jordi Boggiano + * + * @method bool hasEmergency($record) + * @method bool hasAlert($record) + * @method bool hasCritical($record) + * @method bool hasError($record) + * @method bool hasWarning($record) + * @method bool hasNotice($record) + * @method bool hasInfo($record) + * @method bool hasDebug($record) + * + * @method bool hasEmergencyRecords() + * @method bool hasAlertRecords() + * @method bool hasCriticalRecords() + * @method bool hasErrorRecords() + * @method bool hasWarningRecords() + * @method bool hasNoticeRecords() + * @method bool hasInfoRecords() + * @method bool hasDebugRecords() + * + * @method bool hasEmergencyThatContains($message) + * @method bool hasAlertThatContains($message) + * @method bool hasCriticalThatContains($message) + * @method bool hasErrorThatContains($message) + * @method bool hasWarningThatContains($message) + * @method bool hasNoticeThatContains($message) + * @method bool hasInfoThatContains($message) + * @method bool hasDebugThatContains($message) + * + * @method bool hasEmergencyThatMatches($message) + * @method bool hasAlertThatMatches($message) + * @method bool hasCriticalThatMatches($message) + * @method bool hasErrorThatMatches($message) + * @method bool hasWarningThatMatches($message) + * @method bool hasNoticeThatMatches($message) + * @method bool hasInfoThatMatches($message) + * @method bool hasDebugThatMatches($message) + * + * @method bool hasEmergencyThatPasses($message) + * @method bool hasAlertThatPasses($message) + * @method bool hasCriticalThatPasses($message) + * @method bool hasErrorThatPasses($message) + * @method bool hasWarningThatPasses($message) + * @method bool hasNoticeThatPasses($message) + * @method bool hasInfoThatPasses($message) + * @method bool hasDebugThatPasses($message) + */ +class TestHandler extends AbstractProcessingHandler +{ + protected $records = array(); + protected $recordsByLevel = array(); + + public function getRecords() + { + return $this->records; + } + + public function clear() + { + $this->records = array(); + $this->recordsByLevel = array(); + } + + public function hasRecords($level) + { + return isset($this->recordsByLevel[$level]); + } + + public function hasRecord($record, $level) + { + if (is_array($record)) { + $record = $record['message']; + } + + return $this->hasRecordThatPasses(function ($rec) use ($record) { + return $rec['message'] === $record; + }, $level); + } + + public function hasRecordThatContains($message, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + public function hasRecordThatMatches($regex, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + public function hasRecordThatPasses($predicate, $level) + { + if (!is_callable($predicate)) { + throw new \InvalidArgumentException("Expected a callable for hasRecordThatSucceeds"); + } + + if (!isset($this->recordsByLevel[$level])) { + return false; + } + + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if (call_user_func($predicate, $rec, $i)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = constant('Monolog\Logger::' . strtoupper($matches[2])); + if (method_exists($this, $genericMethod)) { + $args[] = $level; + + return call_user_func_array(array($this, $genericMethod), $args); + } + } + + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php new file mode 100644 index 00000000..2732ba3d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Forwards records to multiple handlers suppressing failures of each handler + * and continuing through to give every handler a chance to succeed. + * + * @author Craig D'Amelio + */ +class WhatFailureGroupHandler extends GroupHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + foreach ($this->handlers as $handler) { + try { + $handler->handle($record); + } catch (\Exception $e) { + // What failure? + } catch (\Throwable $e) { + // What failure? + } + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch($records); + } catch (\Exception $e) { + // What failure? + } catch (\Throwable $e) { + // What failure? + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php new file mode 100644 index 00000000..f22cf218 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.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 Monolog\Handler; + +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Logger; + +/** + * Handler sending logs to Zend Monitor + * + * @author Christian Bergau + */ +class ZendMonitorHandler extends AbstractProcessingHandler +{ + /** + * Monolog level / ZendMonitor Custom Event priority map + * + * @var array + */ + protected $levelMap = array( + Logger::DEBUG => 1, + Logger::INFO => 2, + Logger::NOTICE => 3, + Logger::WARNING => 4, + Logger::ERROR => 5, + Logger::CRITICAL => 6, + Logger::ALERT => 7, + Logger::EMERGENCY => 0, + ); + + /** + * Construct + * + * @param int $level + * @param bool $bubble + * @throws MissingExtensionException + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + if (!function_exists('zend_monitor_custom_event')) { + throw new MissingExtensionException('You must have Zend Server installed in order to use this handler'); + } + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->writeZendMonitorCustomEvent( + $this->levelMap[$record['level']], + $record['message'], + $record['formatted'] + ); + } + + /** + * Write a record to Zend Monitor + * + * @param int $level + * @param string $message + * @param array $formatted + */ + protected function writeZendMonitorCustomEvent($level, $message, $formatted) + { + zend_monitor_custom_event($level, $message, $formatted); + } + + /** + * {@inheritdoc} + */ + public function getDefaultFormatter() + { + return new NormalizerFormatter(); + } + + /** + * Get the level map + * + * @return array + */ + public function getLevelMap() + { + return $this->levelMap; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Logger.php b/vendor/monolog/monolog/src/Monolog/Logger.php new file mode 100644 index 00000000..49d00af1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Logger.php @@ -0,0 +1,700 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\HandlerInterface; +use Monolog\Handler\StreamHandler; +use Psr\Log\LoggerInterface; +use Psr\Log\InvalidArgumentException; + +/** + * Monolog log channel + * + * It contains a stack of Handlers and a stack of Processors, + * and uses them to store records that are added to it. + * + * @author Jordi Boggiano + */ +class Logger implements LoggerInterface +{ + /** + * Detailed debug information + */ + const DEBUG = 100; + + /** + * Interesting events + * + * Examples: User logs in, SQL logs. + */ + const INFO = 200; + + /** + * Uncommon events + */ + const NOTICE = 250; + + /** + * Exceptional occurrences that are not errors + * + * Examples: Use of deprecated APIs, poor use of an API, + * undesirable things that are not necessarily wrong. + */ + const WARNING = 300; + + /** + * Runtime errors + */ + const ERROR = 400; + + /** + * Critical conditions + * + * Example: Application component unavailable, unexpected exception. + */ + const CRITICAL = 500; + + /** + * Action must be taken immediately + * + * Example: Entire website down, database unavailable, etc. + * This should trigger the SMS alerts and wake you up. + */ + const ALERT = 550; + + /** + * Urgent alert. + */ + const EMERGENCY = 600; + + /** + * Monolog API version + * + * This is only bumped when API breaks are done and should + * follow the major version of the library + * + * @var int + */ + const API = 1; + + /** + * Logging levels from syslog protocol defined in RFC 5424 + * + * @var array $levels Logging levels + */ + protected static $levels = array( + self::DEBUG => 'DEBUG', + self::INFO => 'INFO', + self::NOTICE => 'NOTICE', + self::WARNING => 'WARNING', + self::ERROR => 'ERROR', + self::CRITICAL => 'CRITICAL', + self::ALERT => 'ALERT', + self::EMERGENCY => 'EMERGENCY', + ); + + /** + * @var \DateTimeZone + */ + protected static $timezone; + + /** + * @var string + */ + protected $name; + + /** + * The handler stack + * + * @var HandlerInterface[] + */ + protected $handlers; + + /** + * Processors that will process all log records + * + * To process records of a single handler instead, add the processor on that specific handler + * + * @var callable[] + */ + protected $processors; + + /** + * @var bool + */ + protected $microsecondTimestamps = true; + + /** + * @param string $name The logging channel + * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. + * @param callable[] $processors Optional array of processors + */ + public function __construct($name, array $handlers = array(), array $processors = array()) + { + $this->name = $name; + $this->handlers = $handlers; + $this->processors = $processors; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Return a new cloned instance with the name changed + * + * @return static + */ + public function withName($name) + { + $new = clone $this; + $new->name = $name; + + return $new; + } + + /** + * Pushes a handler on to the stack. + * + * @param HandlerInterface $handler + * @return $this + */ + public function pushHandler(HandlerInterface $handler) + { + array_unshift($this->handlers, $handler); + + return $this; + } + + /** + * Pops a handler from the stack + * + * @return HandlerInterface + */ + public function popHandler() + { + if (!$this->handlers) { + throw new \LogicException('You tried to pop from an empty handler stack.'); + } + + return array_shift($this->handlers); + } + + /** + * Set handlers, replacing all existing ones. + * + * If a map is passed, keys will be ignored. + * + * @param HandlerInterface[] $handlers + * @return $this + */ + public function setHandlers(array $handlers) + { + $this->handlers = array(); + foreach (array_reverse($handlers) as $handler) { + $this->pushHandler($handler); + } + + return $this; + } + + /** + * @return HandlerInterface[] + */ + public function getHandlers() + { + return $this->handlers; + } + + /** + * Adds a processor on to the stack. + * + * @param callable $callback + * @return $this + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * @return callable[] + */ + public function getProcessors() + { + return $this->processors; + } + + /** + * Control the use of microsecond resolution timestamps in the 'datetime' + * member of new records. + * + * Generating microsecond resolution timestamps by calling + * microtime(true), formatting the result via sprintf() and then parsing + * the resulting string via \DateTime::createFromFormat() can incur + * a measurable runtime overhead vs simple usage of DateTime to capture + * a second resolution timestamp in systems which generate a large number + * of log events. + * + * @param bool $micro True to use microtime() to create timestamps + */ + public function useMicrosecondTimestamps($micro) + { + $this->microsecondTimestamps = (bool) $micro; + } + + /** + * Adds a log record. + * + * @param int $level The logging level + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addRecord($level, $message, array $context = array()) + { + if (!$this->handlers) { + $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); + } + + $levelName = static::getLevelName($level); + + // check if any handler will handle this message so we can return early and save cycles + $handlerKey = null; + reset($this->handlers); + while ($handler = current($this->handlers)) { + if ($handler->isHandling(array('level' => $level))) { + $handlerKey = key($this->handlers); + break; + } + + next($this->handlers); + } + + if (null === $handlerKey) { + return false; + } + + if (!static::$timezone) { + static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); + } + + // php7.1+ always has microseconds enabled, so we do not need this hack + if ($this->microsecondTimestamps && PHP_VERSION_ID < 70100) { + $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone); + } else { + $ts = new \DateTime(null, static::$timezone); + } + $ts->setTimezone(static::$timezone); + + $record = array( + 'message' => (string) $message, + 'context' => $context, + 'level' => $level, + 'level_name' => $levelName, + 'channel' => $this->name, + 'datetime' => $ts, + 'extra' => array(), + ); + + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + + while ($handler = current($this->handlers)) { + if (true === $handler->handle($record)) { + break; + } + + next($this->handlers); + } + + return true; + } + + /** + * Adds a log record at the DEBUG level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addDebug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addInfo($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addNotice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addWarning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addError($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addCritical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addAlert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addEmergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Gets all supported logging levels. + * + * @return array Assoc array with human-readable level names => level codes. + */ + public static function getLevels() + { + return array_flip(static::$levels); + } + + /** + * Gets the name of the logging level. + * + * @param int $level + * @return string + */ + public static function getLevelName($level) + { + if (!isset(static::$levels[$level])) { + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); + } + + return static::$levels[$level]; + } + + /** + * Converts PSR-3 levels to Monolog ones if necessary + * + * @param string|int Level number (monolog) or name (PSR-3) + * @return int + */ + public static function toMonologLevel($level) + { + if (is_string($level) && defined(__CLASS__.'::'.strtoupper($level))) { + return constant(__CLASS__.'::'.strtoupper($level)); + } + + return $level; + } + + /** + * Checks whether the Logger has a handler that listens on the given level + * + * @param int $level + * @return Boolean + */ + public function isHandling($level) + { + $record = array( + 'level' => $level, + ); + + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * Adds a log record at an arbitrary level. + * + * This method allows for compatibility with common interfaces. + * + * @param mixed $level The log level + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function log($level, $message, array $context = array()) + { + $level = static::toMonologLevel($level); + + return $this->addRecord($level, $message, $context); + } + + /** + * Adds a log record at the DEBUG level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function debug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function info($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function notice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function warn($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function warning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function err($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function error($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function crit($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function critical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function alert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function emerg($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function emergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Set the timezone to be used for the timestamp of log records. + * + * This is stored globally for all Logger instances + * + * @param \DateTimeZone $tz Timezone object + */ + public static function setTimezone(\DateTimeZone $tz) + { + self::$timezone = $tz; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php new file mode 100644 index 00000000..1899400d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.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 Monolog\Processor; + +use Monolog\Logger; + +/** + * Injects Git branch and Git commit SHA in all records + * + * @author Nick Otter + * @author Jordi Boggiano + */ +class GitProcessor +{ + private $level; + private static $cache; + + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $record['extra']['git'] = self::getGitInfo(); + + return $record; + } + + private static function getGitInfo() + { + if (self::$cache) { + return self::$cache; + } + + $branches = `git branch -v --no-abbrev`; + if (preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) { + return self::$cache = array( + 'branch' => $matches[1], + 'commit' => $matches[2], + ); + } + + return self::$cache = array(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php new file mode 100644 index 00000000..2691630a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.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 Monolog\Processor; + +use Monolog\Logger; + +/** + * Injects line/file:class/function where the log message came from + * + * Warning: This only works if the handler processes the logs directly. + * If you put the processor on a handler that is behind a FingersCrossedHandler + * for example, the processor will only be called once the trigger level is reached, + * and all the log records will have the same file/line/.. data from the call that + * triggered the FingersCrossedHandler. + * + * @author Jordi Boggiano + */ +class IntrospectionProcessor +{ + private $level; + + private $skipClassesPartials; + + private $skipStackFramesCount; + + private $skipFunctions = array( + 'call_user_func', + 'call_user_func_array', + ); + + public function __construct($level = Logger::DEBUG, array $skipClassesPartials = array(), $skipStackFramesCount = 0) + { + $this->level = Logger::toMonologLevel($level); + $this->skipClassesPartials = array_merge(array('Monolog\\'), $skipClassesPartials); + $this->skipStackFramesCount = $skipStackFramesCount; + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + + // skip first since it's always the current method + array_shift($trace); + // the call_user_func call is also skipped + array_shift($trace); + + $i = 0; + + while ($this->isTraceClassOrSkippedFunction($trace, $i)) { + if (isset($trace[$i]['class'])) { + foreach ($this->skipClassesPartials as $part) { + if (strpos($trace[$i]['class'], $part) !== false) { + $i++; + continue 2; + } + } + } elseif (in_array($trace[$i]['function'], $this->skipFunctions)) { + $i++; + continue; + } + + break; + } + + $i += $this->skipStackFramesCount; + + // we should have the call source now + $record['extra'] = array_merge( + $record['extra'], + array( + 'file' => isset($trace[$i - 1]['file']) ? $trace[$i - 1]['file'] : null, + 'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null, + 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, + 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null, + ) + ); + + return $record; + } + + private function isTraceClassOrSkippedFunction(array $trace, $index) + { + if (!isset($trace[$index])) { + return false; + } + + return isset($trace[$index]['class']) || in_array($trace[$index]['function'], $this->skipFunctions); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php new file mode 100644 index 00000000..0543e929 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.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 Monolog\Processor; + +/** + * Injects memory_get_peak_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryPeakUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_peak_usage($this->realUsage); + $formatted = $this->formatBytes($bytes); + + $record['extra']['memory_peak_usage'] = $formatted; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php new file mode 100644 index 00000000..85f9dc5e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.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 Monolog\Processor; + +/** + * Some methods that are common for all memory processors + * + * @author Rob Jensen + */ +abstract class MemoryProcessor +{ + /** + * @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported. + */ + protected $realUsage; + + /** + * @var bool If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + protected $useFormatting; + + /** + * @param bool $realUsage Set this to true to get the real size of memory allocated from system. + * @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + public function __construct($realUsage = true, $useFormatting = true) + { + $this->realUsage = (boolean) $realUsage; + $this->useFormatting = (boolean) $useFormatting; + } + + /** + * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is + * + * @param int $bytes + * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as is + */ + protected function formatBytes($bytes) + { + $bytes = (int) $bytes; + + if (!$this->useFormatting) { + return $bytes; + } + + if ($bytes > 1024 * 1024) { + return round($bytes / 1024 / 1024, 2).' MB'; + } elseif ($bytes > 1024) { + return round($bytes / 1024, 2).' KB'; + } + + return $bytes . ' B'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php new file mode 100644 index 00000000..2783d656 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.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 Monolog\Processor; + +/** + * Injects memory_get_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_usage($this->realUsage); + $formatted = $this->formatBytes($bytes); + + $record['extra']['memory_usage'] = $formatted; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php new file mode 100644 index 00000000..7c07a7e9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.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 Monolog\Processor; + +use Monolog\Logger; + +/** + * Injects Hg branch and Hg revision number in all records + * + * @author Jonathan A. Schweder + */ +class MercurialProcessor +{ + private $level; + private static $cache; + + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $record['extra']['hg'] = self::getMercurialInfo(); + + return $record; + } + + private static function getMercurialInfo() + { + if (self::$cache) { + return self::$cache; + } + + $result = explode(' ', trim(`hg id -nb`)); + if (count($result) >= 3) { + return self::$cache = array( + 'branch' => $result[1], + 'revision' => $result[2], + ); + } + + return self::$cache = array(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php new file mode 100644 index 00000000..9d3f5590 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.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 Monolog\Processor; + +/** + * Adds value of getmypid into records + * + * @author Andreas Hörnicke + */ +class ProcessIdProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $record['extra']['process_id'] = getmypid(); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php new file mode 100644 index 00000000..c2686ce5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.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 Monolog\Processor; + +/** + * Processes a record's message according to PSR-3 rules + * + * It replaces {foo} with the value from $context['foo'] + * + * @author Jordi Boggiano + */ +class PsrLogMessageProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + if (false === strpos($record['message'], '{')) { + return $record; + } + + $replacements = array(); + foreach ($record['context'] as $key => $val) { + if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) { + $replacements['{'.$key.'}'] = $val; + } elseif (is_object($val)) { + $replacements['{'.$key.'}'] = '[object '.get_class($val).']'; + } else { + $replacements['{'.$key.'}'] = '['.gettype($val).']'; + } + } + + $record['message'] = strtr($record['message'], $replacements); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php new file mode 100644 index 00000000..7e2df2ac --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.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 Monolog\Processor; + +/** + * Adds a tags array into record + * + * @author Martijn Riemers + */ +class TagProcessor +{ + private $tags; + + public function __construct(array $tags = array()) + { + $this->setTags($tags); + } + + public function addTags(array $tags = array()) + { + $this->tags = array_merge($this->tags, $tags); + } + + public function setTags(array $tags = array()) + { + $this->tags = $tags; + } + + public function __invoke(array $record) + { + $record['extra']['tags'] = $this->tags; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php new file mode 100644 index 00000000..812707cd --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.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 Monolog\Processor; + +/** + * Adds a unique identifier into records + * + * @author Simon Mönch + */ +class UidProcessor +{ + private $uid; + + public function __construct($length = 7) + { + if (!is_int($length) || $length > 32 || $length < 1) { + throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); + } + + $this->uid = substr(hash('md5', uniqid('', true)), 0, $length); + } + + public function __invoke(array $record) + { + $record['extra']['uid'] = $this->uid; + + return $record; + } + + /** + * @return string + */ + public function getUid() + { + return $this->uid; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php new file mode 100644 index 00000000..ea1d8978 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects url/method and remote IP of the current web request in all records + * + * @author Jordi Boggiano + */ +class WebProcessor +{ + /** + * @var array|\ArrayAccess + */ + protected $serverData; + + /** + * Default fields + * + * Array is structured as [key in record.extra => key in $serverData] + * + * @var array + */ + protected $extraFields = array( + 'url' => 'REQUEST_URI', + 'ip' => 'REMOTE_ADDR', + 'http_method' => 'REQUEST_METHOD', + 'server' => 'SERVER_NAME', + 'referrer' => 'HTTP_REFERER', + ); + + /** + * @param array|\ArrayAccess $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data + * @param array|null $extraFields Field names and the related key inside $serverData to be added. If not provided it defaults to: url, ip, http_method, server, referrer + */ + public function __construct($serverData = null, array $extraFields = null) + { + if (null === $serverData) { + $this->serverData = &$_SERVER; + } elseif (is_array($serverData) || $serverData instanceof \ArrayAccess) { + $this->serverData = $serverData; + } else { + throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.'); + } + + if (null !== $extraFields) { + if (isset($extraFields[0])) { + foreach (array_keys($this->extraFields) as $fieldName) { + if (!in_array($fieldName, $extraFields)) { + unset($this->extraFields[$fieldName]); + } + } + } else { + $this->extraFields = $extraFields; + } + } + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // skip processing if for some reason request data + // is not present (CLI or wonky SAPIs) + if (!isset($this->serverData['REQUEST_URI'])) { + return $record; + } + + $record['extra'] = $this->appendExtraFields($record['extra']); + + return $record; + } + + /** + * @param string $extraName + * @param string $serverName + * @return $this + */ + public function addExtraField($extraName, $serverName) + { + $this->extraFields[$extraName] = $serverName; + + return $this; + } + + /** + * @param array $extra + * @return array + */ + private function appendExtraFields(array $extra) + { + foreach ($this->extraFields as $extraName => $serverName) { + $extra[$extraName] = isset($this->serverData[$serverName]) ? $this->serverData[$serverName] : null; + } + + if (isset($this->serverData['UNIQUE_ID'])) { + $extra['unique_id'] = $this->serverData['UNIQUE_ID']; + } + + return $extra; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Registry.php b/vendor/monolog/monolog/src/Monolog/Registry.php new file mode 100644 index 00000000..159b751c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Registry.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 Monolog; + +use InvalidArgumentException; + +/** + * Monolog log registry + * + * Allows to get `Logger` instances in the global scope + * via static method calls on this class. + * + * + * $application = new Monolog\Logger('application'); + * $api = new Monolog\Logger('api'); + * + * Monolog\Registry::addLogger($application); + * Monolog\Registry::addLogger($api); + * + * function testLogger() + * { + * Monolog\Registry::api()->addError('Sent to $api Logger instance'); + * Monolog\Registry::application()->addError('Sent to $application Logger instance'); + * } + * + * + * @author Tomas Tatarko + */ +class Registry +{ + /** + * List of all loggers in the registry (by named indexes) + * + * @var Logger[] + */ + private static $loggers = array(); + + /** + * Adds new logging channel to the registry + * + * @param Logger $logger Instance of the logging channel + * @param string|null $name Name of the logging channel ($logger->getName() by default) + * @param bool $overwrite Overwrite instance in the registry if the given name already exists? + * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists + */ + public static function addLogger(Logger $logger, $name = null, $overwrite = false) + { + $name = $name ?: $logger->getName(); + + if (isset(self::$loggers[$name]) && !$overwrite) { + throw new InvalidArgumentException('Logger with the given name already exists'); + } + + self::$loggers[$name] = $logger; + } + + /** + * Checks if such logging channel exists by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function hasLogger($logger) + { + if ($logger instanceof Logger) { + $index = array_search($logger, self::$loggers, true); + + return false !== $index; + } else { + return isset(self::$loggers[$logger]); + } + } + + /** + * Removes instance from registry by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function removeLogger($logger) + { + if ($logger instanceof Logger) { + if (false !== ($idx = array_search($logger, self::$loggers, true))) { + unset(self::$loggers[$idx]); + } + } else { + unset(self::$loggers[$logger]); + } + } + + /** + * Clears the registry + */ + public static function clear() + { + self::$loggers = array(); + } + + /** + * Gets Logger instance from the registry + * + * @param string $name Name of the requested Logger instance + * @throws \InvalidArgumentException If named Logger instance is not in the registry + * @return Logger Requested instance of Logger + */ + public static function getInstance($name) + { + if (!isset(self::$loggers[$name])) { + throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name)); + } + + return self::$loggers[$name]; + } + + /** + * Gets Logger instance from the registry via static method call + * + * @param string $name Name of the requested Logger instance + * @param array $arguments Arguments passed to static method call + * @throws \InvalidArgumentException If named Logger instance is not in the registry + * @return Logger Requested instance of Logger + */ + public static function __callStatic($name, $arguments) + { + return self::getInstance($name); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php new file mode 100644 index 00000000..a9a3f301 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.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 Monolog; + +use Monolog\Handler\TestHandler; + +class ErrorHandlerTest extends \PHPUnit_Framework_TestCase +{ + public function testHandleError() + { + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new ErrorHandler($logger); + + $errHandler->registerErrorHandler(array(E_USER_NOTICE => Logger::EMERGENCY), false); + trigger_error('Foo', E_USER_ERROR); + $this->assertCount(1, $handler->getRecords()); + $this->assertTrue($handler->hasErrorRecords()); + trigger_error('Foo', E_USER_NOTICE); + $this->assertCount(2, $handler->getRecords()); + $this->assertTrue($handler->hasEmergencyRecords()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php new file mode 100644 index 00000000..71c42046 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class ChromePHPFormatterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testDefaultFormat() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + array( + 'message' => 'log', + 'context' => array('from' => 'logger'), + 'extra' => array('ip' => '127.0.0.1'), + ), + 'unknown', + 'error', + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::CRITICAL, + 'level_name' => 'CRITICAL', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + array( + 'message' => 'log', + 'context' => array('from' => 'logger'), + 'extra' => array('ip' => '127.0.0.1'), + ), + 'test : 14', + 'error', + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testFormatWithoutContext() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::DEBUG, + 'level_name' => 'DEBUG', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + 'log', + 'unknown', + 'log', + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::formatBatch + */ + public function testBatchFormatThrowException() + { + $formatter = new ChromePHPFormatter(); + $records = array( + array( + 'level' => Logger::INFO, + 'level_name' => 'INFO', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ), + array( + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log2', + ), + ); + + $this->assertEquals( + array( + array( + 'meh', + 'log', + 'unknown', + 'info', + ), + array( + 'foo', + 'log2', + 'unknown', + 'warn', + ), + ), + $formatter->formatBatch($records) + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php new file mode 100644 index 00000000..90cc48dd --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class ElasticaFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!class_exists("Elastica\Document")) { + $this->markTestSkipped("ruflin/elastica not installed"); + } + } + + /** + * @covers Monolog\Formatter\ElasticaFormatter::__construct + * @covers Monolog\Formatter\ElasticaFormatter::format + * @covers Monolog\Formatter\ElasticaFormatter::getDocument + */ + public function testFormat() + { + // test log message + $msg = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 7, 'bar', 'class' => new \stdClass), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + // expected values + $expected = $msg; + $expected['datetime'] = '1970-01-01T00:00:00.000000+00:00'; + $expected['context'] = array( + 'class' => '[object] (stdClass: {})', + 'foo' => 7, + 0 => 'bar', + ); + + // format log message + $formatter = new ElasticaFormatter('my_index', 'doc_type'); + $doc = $formatter->format($msg); + $this->assertInstanceOf('Elastica\Document', $doc); + + // Document parameters + $params = $doc->getParams(); + $this->assertEquals('my_index', $params['_index']); + $this->assertEquals('doc_type', $params['_type']); + + // Document data values + $data = $doc->getData(); + foreach (array_keys($expected) as $key) { + $this->assertEquals($expected[$key], $data[$key]); + } + } + + /** + * @covers Monolog\Formatter\ElasticaFormatter::getIndex + * @covers Monolog\Formatter\ElasticaFormatter::getType + */ + public function testGetters() + { + $formatter = new ElasticaFormatter('my_index', 'doc_type'); + $this->assertEquals('my_index', $formatter->getIndex()); + $this->assertEquals('doc_type', $formatter->getType()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php new file mode 100644 index 00000000..1b2fd97a --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.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 Monolog\Formatter; + +use Monolog\Logger; +use Monolog\TestCase; + +class FlowdockFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\FlowdockFormatter::format + */ + public function testFormat() + { + $formatter = new FlowdockFormatter('test_source', 'source@test.com'); + $record = $this->getRecord(); + + $expected = array( + 'source' => 'test_source', + 'from_address' => 'source@test.com', + 'subject' => 'in test_source: WARNING - test', + 'content' => 'test', + 'tags' => array('#logs', '#warning', '#test'), + 'project' => 'test_source', + ); + $formatted = $formatter->format($record); + + $this->assertEquals($expected, $formatted['flowdock']); + } + + /** + * @ covers Monolog\Formatter\FlowdockFormatter::formatBatch + */ + public function testFormatBatch() + { + $formatter = new FlowdockFormatter('test_source', 'source@test.com'); + $records = array( + $this->getRecord(Logger::WARNING), + $this->getRecord(Logger::DEBUG), + ); + $formatted = $formatter->formatBatch($records); + + $this->assertArrayHasKey('flowdock', $formatted[0]); + $this->assertArrayHasKey('flowdock', $formatted[1]); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php new file mode 100644 index 00000000..622b2bae --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.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 Monolog\Formatter; + +use Monolog\Logger; +use Monolog\TestCase; + +class FluentdFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\FluentdFormatter::__construct + * @covers Monolog\Formatter\FluentdFormatter::isUsingLevelsInTag + */ + public function testConstruct() + { + $formatter = new FluentdFormatter(); + $this->assertEquals(false, $formatter->isUsingLevelsInTag()); + $formatter = new FluentdFormatter(false); + $this->assertEquals(false, $formatter->isUsingLevelsInTag()); + $formatter = new FluentdFormatter(true); + $this->assertEquals(true, $formatter->isUsingLevelsInTag()); + } + + /** + * @covers Monolog\Formatter\FluentdFormatter::format + */ + public function testFormat() + { + $record = $this->getRecord(Logger::WARNING); + $record['datetime'] = new \DateTime("@0"); + + $formatter = new FluentdFormatter(); + $this->assertEquals( + '["test",0,{"message":"test","extra":[],"level":300,"level_name":"WARNING"}]', + $formatter->format($record) + ); + } + + /** + * @covers Monolog\Formatter\FluentdFormatter::format + */ + public function testFormatWithTag() + { + $record = $this->getRecord(Logger::ERROR); + $record['datetime'] = new \DateTime("@0"); + + $formatter = new FluentdFormatter(true); + $this->assertEquals( + '["test.error",0,{"message":"test","extra":[]}]', + $formatter->format($record) + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php new file mode 100644 index 00000000..e6bd3098 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php @@ -0,0 +1,234 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class GelfMessageFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!class_exists('\Gelf\Message')) { + $this->markTestSkipped("graylog2/gelf-php or mlehner/gelf-php is not installed"); + } + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testDefaultFormatter() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals(0, $message->getTimestamp()); + $this->assertEquals('log', $message->getShortMessage()); + $this->assertEquals('meh', $message->getFacility()); + $this->assertEquals(null, $message->getLine()); + $this->assertEquals(null, $message->getFile()); + $this->assertEquals($this->isLegacy() ? 3 : 'error', $message->getLevel()); + $this->assertNotEmpty($message->getHost()); + + $formatter = new GelfMessageFormatter('mysystem'); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals('mysystem', $message->getHost()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals('test', $message->getFile()); + $this->assertEquals(14, $message->getLine()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + * @expectedException InvalidArgumentException + */ + public function testFormatInvalidFails() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + ); + + $formatter->format($record); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithContext() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_ctxt_from', $message_array); + $this->assertEquals('logger', $message_array['_ctxt_from']); + + // Test with extraPrefix + $formatter = new GelfMessageFormatter(null, null, 'CTX'); + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_CTXfrom', $message_array); + $this->assertEquals('logger', $message_array['_CTXfrom']); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithContextContainingException() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger', 'exception' => array( + 'class' => '\Exception', + 'file' => '/some/file/in/dir.php:56', + 'trace' => array('/some/file/1.php:23', '/some/file/2.php:3'), + )), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $this->assertEquals("/some/file/in/dir.php", $message->getFile()); + $this->assertEquals("56", $message->getLine()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithExtra() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_key', $message_array); + $this->assertEquals('pair', $message_array['_key']); + + // Test with extraPrefix + $formatter = new GelfMessageFormatter(null, 'EXT'); + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_EXTkey', $message_array); + $this->assertEquals('pair', $message_array['_EXTkey']); + } + + public function testFormatWithLargeData() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('exception' => str_repeat(' ', 32767)), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => str_repeat(' ', 32767)), + 'message' => 'log' + ); + $message = $formatter->format($record); + $messageArray = $message->toArray(); + + // 200 for padding + metadata + $length = 200; + + foreach ($messageArray as $key => $value) { + if (!in_array($key, array('level', 'timestamp'))) { + $length += strlen($value); + } + } + + // in graylog2/gelf-php before 1.4.1 empty strings are filtered and won't be included in the message + // though it should be sufficient to ensure that the entire message length does not exceed the maximum + // length being allowed + $this->assertLessThanOrEqual(32766, $length, 'The message length is no longer than the maximum allowed length'); + } + + private function isLegacy() + { + return interface_exists('\Gelf\IMessagePublisher'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php new file mode 100644 index 00000000..c9445f36 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.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 Monolog\Formatter; + +use Monolog\Logger; +use Monolog\TestCase; + +class JsonFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\JsonFormatter::__construct + * @covers Monolog\Formatter\JsonFormatter::getBatchMode + * @covers Monolog\Formatter\JsonFormatter::isAppendingNewlines + */ + public function testConstruct() + { + $formatter = new JsonFormatter(); + $this->assertEquals(JsonFormatter::BATCH_MODE_JSON, $formatter->getBatchMode()); + $this->assertEquals(true, $formatter->isAppendingNewlines()); + $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_NEWLINES, false); + $this->assertEquals(JsonFormatter::BATCH_MODE_NEWLINES, $formatter->getBatchMode()); + $this->assertEquals(false, $formatter->isAppendingNewlines()); + } + + /** + * @covers Monolog\Formatter\JsonFormatter::format + */ + public function testFormat() + { + $formatter = new JsonFormatter(); + $record = $this->getRecord(); + $this->assertEquals(json_encode($record)."\n", $formatter->format($record)); + + $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + $record = $this->getRecord(); + $this->assertEquals(json_encode($record), $formatter->format($record)); + } + + /** + * @covers Monolog\Formatter\JsonFormatter::formatBatch + * @covers Monolog\Formatter\JsonFormatter::formatBatchJson + */ + public function testFormatBatch() + { + $formatter = new JsonFormatter(); + $records = array( + $this->getRecord(Logger::WARNING), + $this->getRecord(Logger::DEBUG), + ); + $this->assertEquals(json_encode($records), $formatter->formatBatch($records)); + } + + /** + * @covers Monolog\Formatter\JsonFormatter::formatBatch + * @covers Monolog\Formatter\JsonFormatter::formatBatchNewlines + */ + public function testFormatBatchNewlines() + { + $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_NEWLINES); + $records = $expected = array( + $this->getRecord(Logger::WARNING), + $this->getRecord(Logger::DEBUG), + ); + array_walk($expected, function (&$value, $key) { + $value = json_encode($value); + }); + $this->assertEquals(implode("\n", $expected), $formatter->formatBatch($records)); + } + + public function testDefFormatWithException() + { + $formatter = new JsonFormatter(); + $exception = new \RuntimeException('Foo'); + $formattedException = $this->formatException($exception); + + $message = $this->formatRecordWithExceptionInContext($formatter, $exception); + + $this->assertContextContainsFormattedException($formattedException, $message); + } + + public function testDefFormatWithPreviousException() + { + $formatter = new JsonFormatter(); + $exception = new \RuntimeException('Foo', 0, new \LogicException('Wut?')); + $formattedPrevException = $this->formatException($exception->getPrevious()); + $formattedException = $this->formatException($exception, $formattedPrevException); + + $message = $this->formatRecordWithExceptionInContext($formatter, $exception); + + $this->assertContextContainsFormattedException($formattedException, $message); + } + + public function testDefFormatWithThrowable() + { + if (!class_exists('Error') || !is_subclass_of('Error', 'Throwable')) { + $this->markTestSkipped('Requires PHP >=7'); + } + + $formatter = new JsonFormatter(); + $throwable = new \Error('Foo'); + $formattedThrowable = $this->formatException($throwable); + + $message = $this->formatRecordWithExceptionInContext($formatter, $throwable); + + $this->assertContextContainsFormattedException($formattedThrowable, $message); + } + + /** + * @param string $expected + * @param string $actual + * + * @internal param string $exception + */ + private function assertContextContainsFormattedException($expected, $actual) + { + $this->assertEquals( + '{"level_name":"CRITICAL","channel":"core","context":{"exception":'.$expected.'},"datetime":null,"extra":[],"message":"foobar"}'."\n", + $actual + ); + } + + /** + * @param JsonFormatter $formatter + * @param \Exception|\Throwable $exception + * + * @return string + */ + private function formatRecordWithExceptionInContext(JsonFormatter $formatter, $exception) + { + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => $exception), + 'datetime' => null, + 'extra' => array(), + 'message' => 'foobar', + )); + return $message; + } + + /** + * @param \Exception|\Throwable $exception + * + * @return string + */ + private function formatExceptionFilePathWithLine($exception) + { + $options = 0; + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + $options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + } + $path = substr(json_encode($exception->getFile(), $options), 1, -1); + return $path . ':' . $exception->getLine(); + } + + /** + * @param \Exception|\Throwable $exception + * + * @param null|string $previous + * + * @return string + */ + private function formatException($exception, $previous = null) + { + $formattedException = + '{"class":"' . get_class($exception) . + '","message":"' . $exception->getMessage() . + '","code":' . $exception->getCode() . + ',"file":"' . $this->formatExceptionFilePathWithLine($exception) . + ($previous ? '","previous":' . $previous : '"') . + '}'; + return $formattedException; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php new file mode 100644 index 00000000..310d93ca --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php @@ -0,0 +1,222 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * @covers Monolog\Formatter\LineFormatter + */ +class LineFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function testDefFormatWithString() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'context' => array(), + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array(), + )); + $this->assertEquals('['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message); + } + + public function testDefFormatWithArrayContext() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array(), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + 'bool' => false, + 'null' => null, + ), + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foo {"foo":"bar","baz":"qux","bool":false,"null":null} []'."\n", $message); + } + + public function testDefFormatExtras() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] {"ip":"127.0.0.1"}'."\n", $message); + } + + public function testFormatExtras() + { + $formatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra.file% %extra%\n", 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test'), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] test {"ip":"127.0.0.1"}'."\n", $message); + } + + public function testContextAndExtraOptionallyNotShownIfEmpty() + { + $formatter = new LineFormatter(null, 'Y-m-d', false, true); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log '."\n", $message); + } + + public function testContextAndExtraReplacement() + { + $formatter = new LineFormatter('%context.foo% => %extra.foo%'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 'bar'), + 'datetime' => new \DateTime, + 'extra' => array('foo' => 'xbar'), + 'message' => 'log', + )); + $this->assertEquals('bar => xbar', $message); + } + + public function testDefFormatWithObject() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('foo' => new TestFoo, 'bar' => new TestBar, 'baz' => array(), 'res' => fopen('php://memory', 'rb')), + 'message' => 'foobar', + )); + + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foobar [] {"foo":"[object] (Monolog\\\\Formatter\\\\TestFoo: {\\"foo\\":\\"foo\\"})","bar":"[object] (Monolog\\\\Formatter\\\\TestBar: bar)","baz":[],"res":"[resource] (stream)"}'."\n", $message); + } + + public function testDefFormatWithException() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => new \RuntimeException('Foo')), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'foobar', + )); + + $path = str_replace('\\/', '/', json_encode(__FILE__)); + + $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException(code: 0): Foo at '.substr($path, 1, -1).':'.(__LINE__ - 8).')"} []'."\n", $message); + } + + public function testDefFormatWithPreviousException() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $previous = new \LogicException('Wut?'); + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => new \RuntimeException('Foo', 0, $previous)), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'foobar', + )); + + $path = str_replace('\\/', '/', json_encode(__FILE__)); + + $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException(code: 0): Foo at '.substr($path, 1, -1).':'.(__LINE__ - 8).', LogicException(code: 0): Wut? at '.substr($path, 1, -1).':'.(__LINE__ - 12).')"} []'."\n", $message); + } + + public function testBatchFormat() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->formatBatch(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + )); + $this->assertEquals('['.date('Y-m-d').'] test.CRITICAL: bar [] []'."\n".'['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message); + } + + public function testFormatShouldStripInlineLineBreaks() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format( + array( + 'message' => "foo\nbar", + 'context' => array(), + 'extra' => array(), + ) + ); + + $this->assertRegExp('/foo bar/', $message); + } + + public function testFormatShouldNotStripInlineLineBreaksWhenFlagIsSet() + { + $formatter = new LineFormatter(null, 'Y-m-d', true); + $message = $formatter->format( + array( + 'message' => "foo\nbar", + 'context' => array(), + 'extra' => array(), + ) + ); + + $this->assertRegExp('/foo\nbar/', $message); + } +} + +class TestFoo +{ + public $foo = 'foo'; +} + +class TestBar +{ + public function __toString() + { + return 'bar'; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php new file mode 100644 index 00000000..6d59b3f3 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.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 Monolog\Formatter; + +use Monolog\TestCase; + +class LogglyFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\LogglyFormatter::__construct + */ + public function testConstruct() + { + $formatter = new LogglyFormatter(); + $this->assertEquals(LogglyFormatter::BATCH_MODE_NEWLINES, $formatter->getBatchMode()); + $formatter = new LogglyFormatter(LogglyFormatter::BATCH_MODE_JSON); + $this->assertEquals(LogglyFormatter::BATCH_MODE_JSON, $formatter->getBatchMode()); + } + + /** + * @covers Monolog\Formatter\LogglyFormatter::format + */ + public function testFormat() + { + $formatter = new LogglyFormatter(); + $record = $this->getRecord(); + $formatted_decoded = json_decode($formatter->format($record), true); + $this->assertArrayHasKey("timestamp", $formatted_decoded); + $this->assertEquals(new \DateTime($formatted_decoded["timestamp"]), $record["datetime"]); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php new file mode 100644 index 00000000..9f6b1cc4 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php @@ -0,0 +1,333 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class LogstashFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function tearDown() + { + \PHPUnit_Framework_Error_Warning::$enabled = true; + + return parent::tearDown(); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testDefaultFormatter() + { + $formatter = new LogstashFormatter('test', 'hostname'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']); + $this->assertEquals('log', $message['@message']); + $this->assertEquals('meh', $message['@fields']['channel']); + $this->assertContains('meh', $message['@tags']); + $this->assertEquals(Logger::ERROR, $message['@fields']['level']); + $this->assertEquals('test', $message['@type']); + $this->assertEquals('hostname', $message['@source']); + + $formatter = new LogstashFormatter('mysystem'); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('mysystem', $message['@type']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('test', $message['@fields']['file']); + $this->assertEquals(14, $message['@fields']['line']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithContext() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('ctxt_from', $message_array); + $this->assertEquals('logger', $message_array['ctxt_from']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, null, 'CTX'); + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('CTXfrom', $message_array); + $this->assertEquals('logger', $message_array['CTXfrom']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithExtra() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('key', $message_array); + $this->assertEquals('pair', $message_array['key']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, 'EXT'); + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('EXTkey', $message_array); + $this->assertEquals('pair', $message_array['EXTkey']); + } + + public function testFormatWithApplicationName() + { + $formatter = new LogstashFormatter('app', 'test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('@type', $message); + $this->assertEquals('app', $message['@type']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testDefaultFormatterV1() + { + $formatter = new LogstashFormatter('test', 'hostname', null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']); + $this->assertEquals("1", $message['@version']); + $this->assertEquals('log', $message['message']); + $this->assertEquals('meh', $message['channel']); + $this->assertEquals('ERROR', $message['level']); + $this->assertEquals('test', $message['type']); + $this->assertEquals('hostname', $message['host']); + + $formatter = new LogstashFormatter('mysystem', null, null, 'ctxt_', LogstashFormatter::V1); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('mysystem', $message['type']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithFileAndLineV1() + { + $formatter = new LogstashFormatter('test', null, null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('test', $message['file']); + $this->assertEquals(14, $message['line']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithContextV1() + { + $formatter = new LogstashFormatter('test', null, null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('ctxt_from', $message); + $this->assertEquals('logger', $message['ctxt_from']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, null, 'CTX', LogstashFormatter::V1); + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('CTXfrom', $message); + $this->assertEquals('logger', $message['CTXfrom']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithExtraV1() + { + $formatter = new LogstashFormatter('test', null, null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('key', $message); + $this->assertEquals('pair', $message['key']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, 'EXT', 'ctxt_', LogstashFormatter::V1); + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('EXTkey', $message); + $this->assertEquals('pair', $message['EXTkey']); + } + + public function testFormatWithApplicationNameV1() + { + $formatter = new LogstashFormatter('app', 'test', null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('type', $message); + $this->assertEquals('app', $message['type']); + } + + public function testFormatWithLatin9Data() + { + if (version_compare(PHP_VERSION, '5.5.0', '<')) { + // Ignore the warning that will be emitted by PHP <5.5.0 + \PHPUnit_Framework_Error_Warning::$enabled = false; + } + $formatter = new LogstashFormatter('test', 'hostname'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => '¯\_(ツ)_/¯', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array( + 'user_agent' => "\xD6WN; FBCR/OrangeEspa\xF1a; Vers\xE3o/4.0; F\xE4rist", + ), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']); + $this->assertEquals('log', $message['@message']); + $this->assertEquals('¯\_(ツ)_/¯', $message['@fields']['channel']); + $this->assertContains('¯\_(ツ)_/¯', $message['@tags']); + $this->assertEquals(Logger::ERROR, $message['@fields']['level']); + $this->assertEquals('test', $message['@type']); + $this->assertEquals('hostname', $message['@source']); + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $this->assertEquals('ÖWN; FBCR/OrangeEspaña; Versão/4.0; Färist', $message['@fields']['user_agent']); + } else { + // PHP <5.5 does not return false for an element encoding failure, + // instead it emits a warning (possibly) and nulls the value. + $this->assertEquals(null, $message['@fields']['user_agent']); + } + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php new file mode 100644 index 00000000..52e699e0 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php @@ -0,0 +1,262 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * @author Florian Plattner + */ +class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!class_exists('MongoDate')) { + $this->markTestSkipped('mongo extension not installed'); + } + } + + public function constructArgumentProvider() + { + return array( + array(1, true, 1, true), + array(0, false, 0, false), + ); + } + + /** + * @param $traceDepth + * @param $traceAsString + * @param $expectedTraceDepth + * @param $expectedTraceAsString + * + * @dataProvider constructArgumentProvider + */ + public function testConstruct($traceDepth, $traceAsString, $expectedTraceDepth, $expectedTraceAsString) + { + $formatter = new MongoDBFormatter($traceDepth, $traceAsString); + + $reflTrace = new \ReflectionProperty($formatter, 'exceptionTraceAsString'); + $reflTrace->setAccessible(true); + $this->assertEquals($expectedTraceAsString, $reflTrace->getValue($formatter)); + + $reflDepth = new\ReflectionProperty($formatter, 'maxNestingLevel'); + $reflDepth->setAccessible(true); + $this->assertEquals($expectedTraceDepth, $reflDepth->getValue($formatter)); + } + + public function testSimpleFormat() + { + $record = array( + 'message' => 'some log message', + 'context' => array(), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(); + $formattedRecord = $formatter->format($record); + + $this->assertCount(7, $formattedRecord); + $this->assertEquals('some log message', $formattedRecord['message']); + $this->assertEquals(array(), $formattedRecord['context']); + $this->assertEquals(Logger::WARNING, $formattedRecord['level']); + $this->assertEquals(Logger::getLevelName(Logger::WARNING), $formattedRecord['level_name']); + $this->assertEquals('test', $formattedRecord['channel']); + $this->assertInstanceOf('\MongoDate', $formattedRecord['datetime']); + $this->assertEquals('0.00000000 1391212800', $formattedRecord['datetime']->__toString()); + $this->assertEquals(array(), $formattedRecord['extra']); + } + + public function testRecursiveFormat() + { + $someObject = new \stdClass(); + $someObject->foo = 'something'; + $someObject->bar = 'stuff'; + + $record = array( + 'message' => 'some log message', + 'context' => array( + 'stuff' => new \DateTime('2014-02-01 02:31:33'), + 'some_object' => $someObject, + 'context_string' => 'some string', + 'context_int' => 123456, + 'except' => new \Exception('exception message', 987), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(); + $formattedRecord = $formatter->format($record); + + $this->assertCount(5, $formattedRecord['context']); + $this->assertInstanceOf('\MongoDate', $formattedRecord['context']['stuff']); + $this->assertEquals('0.00000000 1391221893', $formattedRecord['context']['stuff']->__toString()); + $this->assertEquals( + array( + 'foo' => 'something', + 'bar' => 'stuff', + 'class' => 'stdClass', + ), + $formattedRecord['context']['some_object'] + ); + $this->assertEquals('some string', $formattedRecord['context']['context_string']); + $this->assertEquals(123456, $formattedRecord['context']['context_int']); + + $this->assertCount(5, $formattedRecord['context']['except']); + $this->assertEquals('exception message', $formattedRecord['context']['except']['message']); + $this->assertEquals(987, $formattedRecord['context']['except']['code']); + $this->assertInternalType('string', $formattedRecord['context']['except']['file']); + $this->assertInternalType('integer', $formattedRecord['context']['except']['code']); + $this->assertInternalType('string', $formattedRecord['context']['except']['trace']); + $this->assertEquals('Exception', $formattedRecord['context']['except']['class']); + } + + public function testFormatDepthArray() + { + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => array( + 'property' => 'anything', + 'nest3' => array( + 'nest4' => 'value', + 'property' => 'nothing', + ), + ), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(2); + $formattedResult = $formatter->format($record); + + $this->assertEquals( + array( + 'nest2' => array( + 'property' => 'anything', + 'nest3' => '[...]', + ), + ), + $formattedResult['context'] + ); + } + + public function testFormatDepthArrayInfiniteNesting() + { + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => array( + 'property' => 'something', + 'nest3' => array( + 'property' => 'anything', + 'nest4' => array( + 'property' => 'nothing', + ), + ), + ), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(0); + $formattedResult = $formatter->format($record); + + $this->assertEquals( + array( + 'nest2' => array( + 'property' => 'something', + 'nest3' => array( + 'property' => 'anything', + 'nest4' => array( + 'property' => 'nothing', + ), + ), + ), + ), + $formattedResult['context'] + ); + } + + public function testFormatDepthObjects() + { + $someObject = new \stdClass(); + $someObject->property = 'anything'; + $someObject->nest3 = new \stdClass(); + $someObject->nest3->property = 'nothing'; + $someObject->nest3->nest4 = 'invisible'; + + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => $someObject, + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(2, true); + $formattedResult = $formatter->format($record); + + $this->assertEquals( + array( + 'nest2' => array( + 'property' => 'anything', + 'nest3' => '[...]', + 'class' => 'stdClass', + ), + ), + $formattedResult['context'] + ); + } + + public function testFormatDepthException() + { + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => new \Exception('exception message', 987), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(2, false); + $formattedRecord = $formatter->format($record); + + $this->assertEquals('exception message', $formattedRecord['context']['nest2']['message']); + $this->assertEquals(987, $formattedRecord['context']['nest2']['code']); + $this->assertEquals('[...]', $formattedRecord['context']['nest2']['trace']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php new file mode 100644 index 00000000..57bcdf98 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php @@ -0,0 +1,423 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * @covers Monolog\Formatter\NormalizerFormatter + */ +class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function tearDown() + { + \PHPUnit_Framework_Error_Warning::$enabled = true; + + return parent::tearDown(); + } + + public function testFormat() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $formatted = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array('foo' => new TestFooNorm, 'bar' => new TestBarNorm, 'baz' => array(), 'res' => fopen('php://memory', 'rb')), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + 'inf' => INF, + '-inf' => -INF, + 'nan' => acos(4), + ), + )); + + $this->assertEquals(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => date('Y-m-d'), + 'extra' => array( + 'foo' => '[object] (Monolog\\Formatter\\TestFooNorm: {"foo":"foo"})', + 'bar' => '[object] (Monolog\\Formatter\\TestBarNorm: bar)', + 'baz' => array(), + 'res' => '[resource] (stream)', + ), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + 'inf' => 'INF', + '-inf' => '-INF', + 'nan' => 'NaN', + ), + ), $formatted); + } + + public function testFormatExceptions() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $e = new \LogicException('bar'); + $e2 = new \RuntimeException('foo', 0, $e); + $formatted = $formatter->format(array( + 'exception' => $e2, + )); + + $this->assertGreaterThan(5, count($formatted['exception']['trace'])); + $this->assertTrue(isset($formatted['exception']['previous'])); + unset($formatted['exception']['trace'], $formatted['exception']['previous']); + + $this->assertEquals(array( + 'exception' => array( + 'class' => get_class($e2), + 'message' => $e2->getMessage(), + 'code' => $e2->getCode(), + 'file' => $e2->getFile().':'.$e2->getLine(), + ), + ), $formatted); + } + + public function testFormatSoapFaultException() + { + if (!class_exists('SoapFault')) { + $this->markTestSkipped('Requires the soap extension'); + } + + $formatter = new NormalizerFormatter('Y-m-d'); + $e = new \SoapFault('foo', 'bar', 'hello', 'world'); + $formatted = $formatter->format(array( + 'exception' => $e, + )); + + unset($formatted['exception']['trace']); + + $this->assertEquals(array( + 'exception' => array( + 'class' => 'SoapFault', + 'message' => 'bar', + 'code' => 0, + 'file' => $e->getFile().':'.$e->getLine(), + 'faultcode' => 'foo', + 'faultactor' => 'hello', + 'detail' => 'world', + ), + ), $formatted); + } + + public function testFormatToStringExceptionHandle() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $this->setExpectedException('RuntimeException', 'Could not convert to string'); + $formatter->format(array( + 'myObject' => new TestToStringError(), + )); + } + + public function testBatchFormat() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $formatted = $formatter->formatBatch(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + )); + $this->assertEquals(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => date('Y-m-d'), + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => date('Y-m-d'), + 'extra' => array(), + ), + ), $formatted); + } + + /** + * Test issue #137 + */ + public function testIgnoresRecursiveObjectReferences() + { + // set up the recursion + $foo = new \stdClass(); + $bar = new \stdClass(); + + $foo->bar = $bar; + $bar->foo = $foo; + + // set an error handler to assert that the error is not raised anymore + $that = $this; + set_error_handler(function ($level, $message, $file, $line, $context) use ($that) { + if (error_reporting() & $level) { + restore_error_handler(); + $that->fail("$message should not be raised"); + } + }); + + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + $res = $reflMethod->invoke($formatter, array($foo, $bar), true); + + restore_error_handler(); + + $this->assertEquals(@json_encode(array($foo, $bar)), $res); + } + + public function testIgnoresInvalidTypes() + { + // set up the recursion + $resource = fopen(__FILE__, 'r'); + + // set an error handler to assert that the error is not raised anymore + $that = $this; + set_error_handler(function ($level, $message, $file, $line, $context) use ($that) { + if (error_reporting() & $level) { + restore_error_handler(); + $that->fail("$message should not be raised"); + } + }); + + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + $res = $reflMethod->invoke($formatter, array($resource), true); + + restore_error_handler(); + + $this->assertEquals(@json_encode(array($resource)), $res); + } + + public function testNormalizeHandleLargeArrays() + { + $formatter = new NormalizerFormatter(); + $largeArray = range(1, 2000); + + $res = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array($largeArray), + 'datetime' => new \DateTime, + 'extra' => array(), + )); + + $this->assertCount(1000, $res['context'][0]); + $this->assertEquals('Over 1000 items (2000 total), aborting normalization', $res['context'][0]['...']); + } + + /** + * @expectedException RuntimeException + */ + public function testThrowsOnInvalidEncoding() + { + if (version_compare(PHP_VERSION, '5.5.0', '<')) { + // Ignore the warning that will be emitted by PHP <5.5.0 + \PHPUnit_Framework_Error_Warning::$enabled = false; + } + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + + // send an invalid unicode sequence as a object that can't be cleaned + $record = new \stdClass; + $record->message = "\xB1\x31"; + $res = $reflMethod->invoke($formatter, $record); + if (PHP_VERSION_ID < 50500 && $res === '{"message":null}') { + throw new \RuntimeException('PHP 5.3/5.4 throw a warning and null the value instead of returning false entirely'); + } + } + + public function testConvertsInvalidEncodingAsLatin9() + { + if (version_compare(PHP_VERSION, '5.5.0', '<')) { + // Ignore the warning that will be emitted by PHP <5.5.0 + \PHPUnit_Framework_Error_Warning::$enabled = false; + } + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + + $res = $reflMethod->invoke($formatter, array('message' => "\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE")); + + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $this->assertSame('{"message":"€ŠšŽžŒœŸ"}', $res); + } else { + // PHP <5.5 does not return false for an element encoding failure, + // instead it emits a warning (possibly) and nulls the value. + $this->assertSame('{"message":null}', $res); + } + } + + /** + * @param mixed $in Input + * @param mixed $expect Expected output + * @covers Monolog\Formatter\NormalizerFormatter::detectAndCleanUtf8 + * @dataProvider providesDetectAndCleanUtf8 + */ + public function testDetectAndCleanUtf8($in, $expect) + { + $formatter = new NormalizerFormatter(); + $formatter->detectAndCleanUtf8($in); + $this->assertSame($expect, $in); + } + + public function providesDetectAndCleanUtf8() + { + $obj = new \stdClass; + + return array( + 'null' => array(null, null), + 'int' => array(123, 123), + 'float' => array(123.45, 123.45), + 'bool false' => array(false, false), + 'bool true' => array(true, true), + 'ascii string' => array('abcdef', 'abcdef'), + 'latin9 string' => array("\xB1\x31\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE\xFF", '±1€ŠšŽžŒœŸÿ'), + 'unicode string' => array('¤¦¨´¸¼½¾€ŠšŽžŒœŸ', '¤¦¨´¸¼½¾€ŠšŽžŒœŸ'), + 'empty array' => array(array(), array()), + 'array' => array(array('abcdef'), array('abcdef')), + 'object' => array($obj, $obj), + ); + } + + /** + * @param int $code + * @param string $msg + * @dataProvider providesHandleJsonErrorFailure + */ + public function testHandleJsonErrorFailure($code, $msg) + { + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'handleJsonError'); + $reflMethod->setAccessible(true); + + $this->setExpectedException('RuntimeException', $msg); + $reflMethod->invoke($formatter, $code, 'faked'); + } + + public function providesHandleJsonErrorFailure() + { + return array( + 'depth' => array(JSON_ERROR_DEPTH, 'Maximum stack depth exceeded'), + 'state' => array(JSON_ERROR_STATE_MISMATCH, 'Underflow or the modes mismatch'), + 'ctrl' => array(JSON_ERROR_CTRL_CHAR, 'Unexpected control character found'), + 'default' => array(-1, 'Unknown error'), + ); + } + + public function testExceptionTraceWithArgs() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('Not supported in HHVM since it detects errors differently'); + } + + // This happens i.e. in React promises or Guzzle streams where stream wrappers are registered + // and no file or line are included in the trace because it's treated as internal function + set_error_handler(function ($errno, $errstr, $errfile, $errline) { + throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); + }); + + try { + // This will contain $resource and $wrappedResource as arguments in the trace item + $resource = fopen('php://memory', 'rw+'); + fwrite($resource, 'test_resource'); + $wrappedResource = new TestFooNorm; + $wrappedResource->foo = $resource; + // Just do something stupid with a resource/wrapped resource as argument + array_keys($wrappedResource); + } catch (\Exception $e) { + restore_error_handler(); + } + + $formatter = new NormalizerFormatter(); + $record = array('context' => array('exception' => $e)); + $result = $formatter->format($record); + + $this->assertRegExp( + '%"resource":"\[resource\] \(stream\)"%', + $result['context']['exception']['trace'][0] + ); + + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $pattern = '%"wrappedResource":"\[object\] \(Monolog\\\\\\\\Formatter\\\\\\\\TestFooNorm: \)"%'; + } else { + $pattern = '%\\\\"foo\\\\":null%'; + } + + // Tests that the wrapped resource is ignored while encoding, only works for PHP <= 5.4 + $this->assertRegExp( + $pattern, + $result['context']['exception']['trace'][0] + ); + } +} + +class TestFooNorm +{ + public $foo = 'foo'; +} + +class TestBarNorm +{ + public function __toString() + { + return 'bar'; + } +} + +class TestStreamFoo +{ + public $foo; + public $resource; + + public function __construct($resource) + { + $this->resource = $resource; + $this->foo = 'BAR'; + } + + public function __toString() + { + fseek($this->resource, 0); + + return $this->foo . ' - ' . (string) stream_get_contents($this->resource); + } +} + +class TestToStringError +{ + public function __toString() + { + throw new \RuntimeException('Could not convert to string'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.php new file mode 100644 index 00000000..b1c8fd49 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.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 Monolog\Formatter; + +class ScalarFormatterTest extends \PHPUnit_Framework_TestCase +{ + private $formatter; + + public function setUp() + { + $this->formatter = new ScalarFormatter(); + } + + public function buildTrace(\Exception $e) + { + $data = array(); + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data[] = $frame['file'].':'.$frame['line']; + } else { + $data[] = json_encode($frame); + } + } + + return $data; + } + + public function encodeJson($data) + { + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return json_encode($data); + } + + public function testFormat() + { + $exception = new \Exception('foo'); + $formatted = $this->formatter->format(array( + 'foo' => 'string', + 'bar' => 1, + 'baz' => false, + 'bam' => array(1, 2, 3), + 'bat' => array('foo' => 'bar'), + 'bap' => \DateTime::createFromFormat(\DateTime::ISO8601, '1970-01-01T00:00:00+0000'), + 'ban' => $exception, + )); + + $this->assertSame(array( + 'foo' => 'string', + 'bar' => 1, + 'baz' => false, + 'bam' => $this->encodeJson(array(1, 2, 3)), + 'bat' => $this->encodeJson(array('foo' => 'bar')), + 'bap' => '1970-01-01 00:00:00', + 'ban' => $this->encodeJson(array( + 'class' => get_class($exception), + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + 'trace' => $this->buildTrace($exception), + )), + ), $formatted); + } + + public function testFormatWithErrorContext() + { + $context = array('file' => 'foo', 'line' => 1); + $formatted = $this->formatter->format(array( + 'context' => $context, + )); + + $this->assertSame(array( + 'context' => $this->encodeJson($context), + ), $formatted); + } + + public function testFormatWithExceptionContext() + { + $exception = new \Exception('foo'); + $formatted = $this->formatter->format(array( + 'context' => array( + 'exception' => $exception, + ), + )); + + $this->assertSame(array( + 'context' => $this->encodeJson(array( + 'exception' => array( + 'class' => get_class($exception), + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + 'trace' => $this->buildTrace($exception), + ), + )), + ), $formatted); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php new file mode 100644 index 00000000..52f15a36 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class WildfireFormatterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testDefaultFormat() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '125|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},' + .'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testFormatWithFileAndLine() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '129|[{"Type":"ERROR","File":"test","Line":14,"Label":"meh"},' + .'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testFormatWithoutContext() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '58|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},"log"]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::formatBatch + * @expectedException BadMethodCallException + */ + public function testBatchFormatThrowException() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $wildfire->formatBatch(array($record)); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testTableFormat() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'table-channel', + 'context' => array( + WildfireFormatter::TABLE => array( + array('col1', 'col2', 'col3'), + array('val1', 'val2', 'val3'), + array('foo1', 'foo2', 'foo3'), + array('bar1', 'bar2', 'bar3'), + ), + ), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'table-message', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '171|[{"Type":"TABLE","File":"","Line":"","Label":"table-channel: table-message"},[["col1","col2","col3"],["val1","val2","val3"],["foo1","foo2","foo3"],["bar1","bar2","bar3"]]]|', + $message + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php new file mode 100644 index 00000000..568eb9da --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Processor\WebProcessor; + +class AbstractHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\AbstractHandler::__construct + * @covers Monolog\Handler\AbstractHandler::getLevel + * @covers Monolog\Handler\AbstractHandler::setLevel + * @covers Monolog\Handler\AbstractHandler::getBubble + * @covers Monolog\Handler\AbstractHandler::setBubble + * @covers Monolog\Handler\AbstractHandler::getFormatter + * @covers Monolog\Handler\AbstractHandler::setFormatter + */ + public function testConstructAndGetSet() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false)); + $this->assertEquals(Logger::WARNING, $handler->getLevel()); + $this->assertEquals(false, $handler->getBubble()); + + $handler->setLevel(Logger::ERROR); + $handler->setBubble(true); + $handler->setFormatter($formatter = new LineFormatter); + $this->assertEquals(Logger::ERROR, $handler->getLevel()); + $this->assertEquals(true, $handler->getBubble()); + $this->assertSame($formatter, $handler->getFormatter()); + } + + /** + * @covers Monolog\Handler\AbstractHandler::handleBatch + */ + public function testHandleBatch() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $handler->expects($this->exactly(2)) + ->method('handle'); + $handler->handleBatch(array($this->getRecord(), $this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractHandler::isHandling + */ + public function testIsHandling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false)); + $this->assertTrue($handler->isHandling($this->getRecord())); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractHandler::__construct + */ + public function testHandlesPsrStyleLevels() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array('warning', false)); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + $handler->setLevel('debug'); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractHandler::getFormatter + * @covers Monolog\Handler\AbstractHandler::getDefaultFormatter + */ + public function testGetFormatterInitializesDefault() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $this->assertInstanceOf('Monolog\Formatter\LineFormatter', $handler->getFormatter()); + } + + /** + * @covers Monolog\Handler\AbstractHandler::pushProcessor + * @covers Monolog\Handler\AbstractHandler::popProcessor + * @expectedException LogicException + */ + public function testPushPopProcessor() + { + $logger = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + + $logger->pushProcessor($processor1); + $logger->pushProcessor($processor2); + + $this->assertEquals($processor2, $logger->popProcessor()); + $this->assertEquals($processor1, $logger->popProcessor()); + $logger->popProcessor(); + } + + /** + * @covers Monolog\Handler\AbstractHandler::pushProcessor + * @expectedException InvalidArgumentException + */ + public function testPushProcessorWithNonCallable() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + + $handler->pushProcessor(new \stdClass()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php new file mode 100644 index 00000000..24d4f63c --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Processor\WebProcessor; + +class AbstractProcessingHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleLowerLevelMessage() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, true)); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleBubbling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, true)); + $this->assertFalse($handler->handle($this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleNotBubbling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, false)); + $this->assertTrue($handler->handle($this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleIsFalseWhenNotHandled() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, false)); + $this->assertTrue($handler->handle($this->getRecord())); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::processRecord + */ + public function testProcessRecord() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler'); + $handler->pushProcessor(new WebProcessor(array( + 'REQUEST_URI' => '', + 'REQUEST_METHOD' => '', + 'REMOTE_ADDR' => '', + 'SERVER_NAME' => '', + 'UNIQUE_ID' => '', + ))); + $handledRecord = null; + $handler->expects($this->once()) + ->method('write') + ->will($this->returnCallback(function ($record) use (&$handledRecord) { + $handledRecord = $record; + })) + ; + $handler->handle($this->getRecord()); + $this->assertEquals(6, count($handledRecord['extra'])); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php new file mode 100644 index 00000000..8e0e7237 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Connection\AMQPConnection; + +/** + * @covers Monolog\Handler\RotatingFileHandler + */ +class AmqpHandlerTest extends TestCase +{ + public function testHandleAmqpExt() + { + if (!class_exists('AMQPConnection') || !class_exists('AMQPExchange')) { + $this->markTestSkipped("amqp-php not installed"); + } + + if (!class_exists('AMQPChannel')) { + $this->markTestSkipped("Please update AMQP to version >= 1.0"); + } + + $messages = array(); + + $exchange = $this->getMock('AMQPExchange', array('publish', 'setName'), array(), '', false); + $exchange->expects($this->once()) + ->method('setName') + ->with('log') + ; + $exchange->expects($this->any()) + ->method('publish') + ->will($this->returnCallback(function ($message, $routing_key, $flags = 0, $attributes = array()) use (&$messages) { + $messages[] = array($message, $routing_key, $flags, $attributes); + })) + ; + + $handler = new AmqpHandler($exchange, 'log'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + array( + 'message' => 'test', + 'context' => array( + 'data' => array(), + 'foo' => 34, + ), + 'level' => 300, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'extra' => array(), + ), + 'warn.test', + 0, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ), + ); + + $handler->handle($record); + + $this->assertCount(1, $messages); + $messages[0][0] = json_decode($messages[0][0], true); + unset($messages[0][0]['datetime']); + $this->assertEquals($expected, $messages[0]); + } + + public function testHandlePhpAmqpLib() + { + if (!class_exists('PhpAmqpLib\Connection\AMQPConnection')) { + $this->markTestSkipped("php-amqplib not installed"); + } + + $messages = array(); + + $exchange = $this->getMock('PhpAmqpLib\Channel\AMQPChannel', array('basic_publish', '__destruct'), array(), '', false); + + $exchange->expects($this->any()) + ->method('basic_publish') + ->will($this->returnCallback(function (AMQPMessage $msg, $exchange = "", $routing_key = "", $mandatory = false, $immediate = false, $ticket = null) use (&$messages) { + $messages[] = array($msg, $exchange, $routing_key, $mandatory, $immediate, $ticket); + })) + ; + + $handler = new AmqpHandler($exchange, 'log'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + array( + 'message' => 'test', + 'context' => array( + 'data' => array(), + 'foo' => 34, + ), + 'level' => 300, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'extra' => array(), + ), + 'log', + 'warn.test', + false, + false, + null, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ), + ); + + $handler->handle($record); + + $this->assertCount(1, $messages); + + /* @var $msg AMQPMessage */ + $msg = $messages[0][0]; + $messages[0][0] = json_decode($msg->body, true); + $messages[0][] = $msg->get_properties(); + unset($messages[0][0]['datetime']); + + $this->assertEquals($expected, $messages[0]); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php new file mode 100644 index 00000000..ffb1d746 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\BrowserConsoleHandlerTest + */ +class BrowserConsoleHandlerTest extends TestCase +{ + protected function setUp() + { + BrowserConsoleHandler::reset(); + } + + protected function generateScript() + { + $reflMethod = new \ReflectionMethod('Monolog\Handler\BrowserConsoleHandler', 'generateScript'); + $reflMethod->setAccessible(true); + + return $reflMethod->invoke(null); + } + + public function testStyling() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, 'foo[[bar]]{color: red}')); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testEscaping() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, "[foo] [[\"bar\n[baz]\"]]{color: red}")); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testAutolabel() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, '[[foo]]{macro: autolabel}')); + $handler->handle($this->getRecord(Logger::DEBUG, '[[bar]]{macro: autolabel}')); + $handler->handle($this->getRecord(Logger::DEBUG, '[[foo]]{macro: autolabel}')); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testContext() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, 'test', array('foo' => 'bar'))); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testConcurrentHandlers() + { + $handler1 = new BrowserConsoleHandler(); + $handler1->setFormatter($this->getIdentityFormatter()); + + $handler2 = new BrowserConsoleHandler(); + $handler2->setFormatter($this->getIdentityFormatter()); + + $handler1->handle($this->getRecord(Logger::DEBUG, 'test1')); + $handler2->handle($this->getRecord(Logger::DEBUG, 'test2')); + $handler1->handle($this->getRecord(Logger::DEBUG, 'test3')); + $handler2->handle($this->getRecord(Logger::DEBUG, 'test4')); + + $expected = <<assertEquals($expected, $this->generateScript()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php new file mode 100644 index 00000000..da8b3c39 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class BufferHandlerTest extends TestCase +{ + private $shutdownCheckHandler; + + /** + * @covers Monolog\Handler\BufferHandler::__construct + * @covers Monolog\Handler\BufferHandler::handle + * @covers Monolog\Handler\BufferHandler::close + */ + public function testHandleBuffers() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->close(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + + /** + * @covers Monolog\Handler\BufferHandler::close + * @covers Monolog\Handler\BufferHandler::flush + */ + public function testPropagatesRecordsAtEndOfRequest() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->shutdownCheckHandler = $test; + register_shutdown_function(array($this, 'checkPropagation')); + } + + public function checkPropagation() + { + if (!$this->shutdownCheckHandler->hasWarningRecords() || !$this->shutdownCheckHandler->hasDebugRecords()) { + echo '!!! BufferHandlerTest::testPropagatesRecordsAtEndOfRequest failed to verify that the messages have been propagated' . PHP_EOL; + exit(1); + } + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleBufferLimit() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 2); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleBufferLimitWithFlushOnOverflow() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 3, Logger::DEBUG, true, true); + + // send two records + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertCount(0, $test->getRecords()); + + // overflow + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertCount(3, $test->getRecords()); + + // should buffer again + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertCount(3, $test->getRecords()); + + $handler->close(); + $this->assertCount(5, $test->getRecords()); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleLevel() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 0, Logger::INFO); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::flush + */ + public function testFlush() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 0); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->flush(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->flush(); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php new file mode 100644 index 00000000..ef3cd1cb --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\ChromePHPHandler + */ +class ChromePHPHandlerTest extends TestCase +{ + protected function setUp() + { + TestChromePHPHandler::reset(); + $_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; Chrome/1.0'; + } + + public function testHeaders() + { + $handler = new TestChromePHPHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + 'test', + 'test', + ), + 'request_uri' => '', + )))), + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testHeadersOverflow() + { + $handler = new TestChromePHPHandler(); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 150 * 1024))); + + // overflow chrome headers limit + $handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 100 * 1024))); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + array( + 'test', + 'test', + 'unknown', + 'log', + ), + array( + 'test', + str_repeat('a', 150 * 1024), + 'unknown', + 'warn', + ), + array( + 'monolog', + 'Incomplete logs, chrome header size limit reached', + 'unknown', + 'warn', + ), + ), + 'request_uri' => '', + )))), + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testConcurrentHandlers() + { + $handler = new TestChromePHPHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $handler2 = new TestChromePHPHandler(); + $handler2->setFormatter($this->getIdentityFormatter()); + $handler2->handle($this->getRecord(Logger::DEBUG)); + $handler2->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + 'test', + 'test', + 'test', + 'test', + ), + 'request_uri' => '', + )))), + ); + + $this->assertEquals($expected, $handler2->getHeaders()); + } +} + +class TestChromePHPHandler extends ChromePHPHandler +{ + protected $headers = array(); + + public static function reset() + { + self::$initialized = false; + self::$overflowed = false; + self::$sendHeaders = true; + self::$json['rows'] = array(); + } + + protected function sendHeader($header, $content) + { + $this->headers[$header] = $content; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php new file mode 100644 index 00000000..9fc4b388 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class CouchDBHandlerTest extends TestCase +{ + public function testHandle() + { + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new CouchDBHandler(); + + try { + $handler->handle($record); + } catch (\RuntimeException $e) { + $this->markTestSkipped('Could not connect to couchdb server on http://localhost:5984'); + } + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php new file mode 100644 index 00000000..e2aff868 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class DeduplicationHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + */ + public function testFlushPassthruIfAllRecordsUnderTrigger() + { + $test = new TestHandler(); + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + + $handler->flush(); + + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + */ + public function testFlushPassthruIfEmptyLog() + { + $test = new TestHandler(); + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $handler->handle($this->getRecord(Logger::ERROR, 'Foo:bar')); + $handler->handle($this->getRecord(Logger::CRITICAL, "Foo\nbar")); + + $handler->flush(); + + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + * @covers Monolog\Handler\DeduplicationHandler::isDuplicate + * @depends testFlushPassthruIfEmptyLog + */ + public function testFlushSkipsIfLogExists() + { + $test = new TestHandler(); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $handler->handle($this->getRecord(Logger::ERROR, 'Foo:bar')); + $handler->handle($this->getRecord(Logger::CRITICAL, "Foo\nbar")); + + $handler->flush(); + + $this->assertFalse($test->hasErrorRecords()); + $this->assertFalse($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + * @covers Monolog\Handler\DeduplicationHandler::isDuplicate + * @depends testFlushPassthruIfEmptyLog + */ + public function testFlushPassthruIfLogTooOld() + { + $test = new TestHandler(); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $record = $this->getRecord(Logger::ERROR); + $record['datetime']->modify('+62seconds'); + $handler->handle($record); + $record = $this->getRecord(Logger::CRITICAL); + $record['datetime']->modify('+62seconds'); + $handler->handle($record); + + $handler->flush(); + + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + * @covers Monolog\Handler\DeduplicationHandler::isDuplicate + * @covers Monolog\Handler\DeduplicationHandler::collectLogs + */ + public function testGcOldLogs() + { + $test = new TestHandler(); + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + // handle two records from yesterday, and one recent + $record = $this->getRecord(Logger::ERROR); + $record['datetime']->modify('-1day -10seconds'); + $handler->handle($record); + $record2 = $this->getRecord(Logger::CRITICAL); + $record2['datetime']->modify('-1day -10seconds'); + $handler->handle($record2); + $record3 = $this->getRecord(Logger::CRITICAL); + $record3['datetime']->modify('-30seconds'); + $handler->handle($record3); + + // log is written as none of them are duplicate + $handler->flush(); + $this->assertSame( + $record['datetime']->getTimestamp() . ":ERROR:test\n" . + $record2['datetime']->getTimestamp() . ":CRITICAL:test\n" . + $record3['datetime']->getTimestamp() . ":CRITICAL:test\n", + file_get_contents(sys_get_temp_dir() . '/monolog_dedup.log') + ); + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + + // clear test handler + $test->clear(); + $this->assertFalse($test->hasErrorRecords()); + $this->assertFalse($test->hasCriticalRecords()); + + // log new records, duplicate log gets GC'd at the end of this flush call + $handler->handle($record = $this->getRecord(Logger::ERROR)); + $handler->handle($record2 = $this->getRecord(Logger::CRITICAL)); + $handler->flush(); + + // log should now contain the new errors and the previous one that was recent enough + $this->assertSame( + $record3['datetime']->getTimestamp() . ":CRITICAL:test\n" . + $record['datetime']->getTimestamp() . ":ERROR:test\n" . + $record2['datetime']->getTimestamp() . ":CRITICAL:test\n", + file_get_contents(sys_get_temp_dir() . '/monolog_dedup.log') + ); + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + public static function tearDownAfterClass() + { + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php new file mode 100644 index 00000000..d67da90a --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class DoctrineCouchDBHandlerTest extends TestCase +{ + protected function setup() + { + if (!class_exists('Doctrine\CouchDB\CouchDBClient')) { + $this->markTestSkipped('The "doctrine/couchdb" package is not installed'); + } + } + + public function testHandle() + { + $client = $this->getMockBuilder('Doctrine\\CouchDB\\CouchDBClient') + ->setMethods(array('postDocument')) + ->disableOriginalConstructor() + ->getMock(); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + 'message' => 'test', + 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34), + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'datetime' => $record['datetime']->format('Y-m-d H:i:s'), + 'extra' => array(), + ); + + $client->expects($this->once()) + ->method('postDocument') + ->with($expected); + + $handler = new DoctrineCouchDBHandler($client); + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.php new file mode 100644 index 00000000..f3a69680 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; + +class DynamoDbHandlerTest extends TestCase +{ + private $client; + + public function setUp() + { + if (!class_exists('Aws\DynamoDb\DynamoDbClient')) { + $this->markTestSkipped('aws/aws-sdk-php not installed'); + } + + $this->client = $this->getMockBuilder('Aws\DynamoDb\DynamoDbClient') + ->setMethods(array('formatAttributes', '__call')) + ->disableOriginalConstructor()->getMock(); + } + + public function testConstruct() + { + $this->assertInstanceOf('Monolog\Handler\DynamoDbHandler', new DynamoDbHandler($this->client, 'foo')); + } + + public function testInterface() + { + $this->assertInstanceOf('Monolog\Handler\HandlerInterface', new DynamoDbHandler($this->client, 'foo')); + } + + public function testGetFormatter() + { + $handler = new DynamoDbHandler($this->client, 'foo'); + $this->assertInstanceOf('Monolog\Formatter\ScalarFormatter', $handler->getFormatter()); + } + + public function testHandle() + { + $record = $this->getRecord(); + $formatter = $this->getMock('Monolog\Formatter\FormatterInterface'); + $formatted = array('foo' => 1, 'bar' => 2); + $handler = new DynamoDbHandler($this->client, 'foo'); + $handler->setFormatter($formatter); + + $formatter + ->expects($this->once()) + ->method('format') + ->with($record) + ->will($this->returnValue($formatted)); + $this->client + ->expects($this->once()) + ->method('formatAttributes') + ->with($this->isType('array')) + ->will($this->returnValue($formatted)); + $this->client + ->expects($this->once()) + ->method('__call') + ->with('putItem', array(array( + 'TableName' => 'foo', + 'Item' => $formatted, + ))); + + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php new file mode 100644 index 00000000..1687074b --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.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 Monolog\Handler; + +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\TestCase; +use Monolog\Logger; +use Elastica\Client; +use Elastica\Request; +use Elastica\Response; + +class ElasticSearchHandlerTest extends TestCase +{ + /** + * @var Client mock + */ + protected $client; + + /** + * @var array Default handler options + */ + protected $options = array( + 'index' => 'my_index', + 'type' => 'doc_type', + ); + + public function setUp() + { + // Elastica lib required + if (!class_exists("Elastica\Client")) { + $this->markTestSkipped("ruflin/elastica not installed"); + } + + // base mock Elastica Client object + $this->client = $this->getMockBuilder('Elastica\Client') + ->setMethods(array('addDocuments')) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::write + * @covers Monolog\Handler\ElasticSearchHandler::handleBatch + * @covers Monolog\Handler\ElasticSearchHandler::bulkSend + * @covers Monolog\Handler\ElasticSearchHandler::getDefaultFormatter + */ + public function testHandle() + { + // log message + $msg = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 7, 'bar', 'class' => new \stdClass), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + // format expected result + $formatter = new ElasticaFormatter($this->options['index'], $this->options['type']); + $expected = array($formatter->format($msg)); + + // setup ES client mock + $this->client->expects($this->any()) + ->method('addDocuments') + ->with($expected); + + // perform tests + $handler = new ElasticSearchHandler($this->client, $this->options); + $handler->handle($msg); + $handler->handleBatch(array($msg)); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::setFormatter + */ + public function testSetFormatter() + { + $handler = new ElasticSearchHandler($this->client); + $formatter = new ElasticaFormatter('index_new', 'type_new'); + $handler->setFormatter($formatter); + $this->assertInstanceOf('Monolog\Formatter\ElasticaFormatter', $handler->getFormatter()); + $this->assertEquals('index_new', $handler->getFormatter()->getIndex()); + $this->assertEquals('type_new', $handler->getFormatter()->getType()); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::setFormatter + * @expectedException InvalidArgumentException + * @expectedExceptionMessage ElasticSearchHandler is only compatible with ElasticaFormatter + */ + public function testSetFormatterInvalid() + { + $handler = new ElasticSearchHandler($this->client); + $formatter = new NormalizerFormatter(); + $handler->setFormatter($formatter); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::__construct + * @covers Monolog\Handler\ElasticSearchHandler::getOptions + */ + public function testOptions() + { + $expected = array( + 'index' => $this->options['index'], + 'type' => $this->options['type'], + 'ignore_error' => false, + ); + $handler = new ElasticSearchHandler($this->client, $this->options); + $this->assertEquals($expected, $handler->getOptions()); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::bulkSend + * @dataProvider providerTestConnectionErrors + */ + public function testConnectionErrors($ignore, $expectedError) + { + $clientOpts = array('host' => '127.0.0.1', 'port' => 1); + $client = new Client($clientOpts); + $handlerOpts = array('ignore_error' => $ignore); + $handler = new ElasticSearchHandler($client, $handlerOpts); + + if ($expectedError) { + $this->setExpectedException($expectedError[0], $expectedError[1]); + $handler->handle($this->getRecord()); + } else { + $this->assertFalse($handler->handle($this->getRecord())); + } + } + + /** + * @return array + */ + public function providerTestConnectionErrors() + { + return array( + array(false, array('RuntimeException', 'Error sending messages to Elasticsearch')), + array(true, false), + ); + } + + /** + * Integration test using localhost Elastic Search server + * + * @covers Monolog\Handler\ElasticSearchHandler::__construct + * @covers Monolog\Handler\ElasticSearchHandler::handleBatch + * @covers Monolog\Handler\ElasticSearchHandler::bulkSend + * @covers Monolog\Handler\ElasticSearchHandler::getDefaultFormatter + */ + public function testHandleIntegration() + { + $msg = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 7, 'bar', 'class' => new \stdClass), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $expected = $msg; + $expected['datetime'] = $msg['datetime']->format(\DateTime::ISO8601); + $expected['context'] = array( + 'class' => '[object] (stdClass: {})', + 'foo' => 7, + 0 => 'bar', + ); + + $client = new Client(); + $handler = new ElasticSearchHandler($client, $this->options); + try { + $handler->handleBatch(array($msg)); + } catch (\RuntimeException $e) { + $this->markTestSkipped("Cannot connect to Elastic Search server on localhost"); + } + + // check document id from ES server response + $documentId = $this->getCreatedDocId($client->getLastResponse()); + $this->assertNotEmpty($documentId, 'No elastic document id received'); + + // retrieve document source from ES and validate + $document = $this->getDocSourceFromElastic( + $client, + $this->options['index'], + $this->options['type'], + $documentId + ); + $this->assertEquals($expected, $document); + + // remove test index from ES + $client->request("/{$this->options['index']}", Request::DELETE); + } + + /** + * Return last created document id from ES response + * @param Response $response Elastica Response object + * @return string|null + */ + protected function getCreatedDocId(Response $response) + { + $data = $response->getData(); + if (!empty($data['items'][0]['create']['_id'])) { + return $data['items'][0]['create']['_id']; + } + } + + /** + * Retrieve document by id from Elasticsearch + * @param Client $client Elastica client + * @param string $index + * @param string $type + * @param string $documentId + * @return array + */ + protected function getDocSourceFromElastic(Client $client, $index, $type, $documentId) + { + $resp = $client->request("/{$index}/{$type}/{$documentId}", Request::GET); + $data = $resp->getData(); + if (!empty($data['_source'])) { + return $data['_source']; + } + + return array(); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php new file mode 100644 index 00000000..99785cbb --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +function error_log() +{ + $GLOBALS['error_log'][] = func_get_args(); +} + +class ErrorLogHandlerTest extends TestCase +{ + protected function setUp() + { + $GLOBALS['error_log'] = array(); + } + + /** + * @covers Monolog\Handler\ErrorLogHandler::__construct + * @expectedException InvalidArgumentException + * @expectedExceptionMessage The given message type "42" is not supported + */ + public function testShouldNotAcceptAnInvalidTypeOnContructor() + { + new ErrorLogHandler(42); + } + + /** + * @covers Monolog\Handler\ErrorLogHandler::write + */ + public function testShouldLogMessagesUsingErrorLogFuncion() + { + $type = ErrorLogHandler::OPERATING_SYSTEM; + $handler = new ErrorLogHandler($type); + $handler->setFormatter(new LineFormatter('%channel%.%level_name%: %message% %context% %extra%', null, true)); + $handler->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + + $this->assertSame("test.ERROR: Foo\nBar\r\n\r\nBaz [] []", $GLOBALS['error_log'][0][0]); + $this->assertSame($GLOBALS['error_log'][0][1], $type); + + $handler = new ErrorLogHandler($type, Logger::DEBUG, true, true); + $handler->setFormatter(new LineFormatter(null, null, true)); + $handler->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + + $this->assertStringMatchesFormat('[%s] test.ERROR: Foo', $GLOBALS['error_log'][1][0]); + $this->assertSame($GLOBALS['error_log'][1][1], $type); + + $this->assertStringMatchesFormat('Bar', $GLOBALS['error_log'][2][0]); + $this->assertSame($GLOBALS['error_log'][2][1], $type); + + $this->assertStringMatchesFormat('Baz [] []', $GLOBALS['error_log'][3][0]); + $this->assertSame($GLOBALS['error_log'][3][1], $type); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.php new file mode 100644 index 00000000..31b7686a --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.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 Monolog\Handler; + +use Monolog\Logger; +use Monolog\TestCase; + +class FilterHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\FilterHandler::isHandling + */ + public function testIsHandling() + { + $test = new TestHandler(); + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::INFO))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::NOTICE))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::WARNING))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::ERROR))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::CRITICAL))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::ALERT))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::EMERGENCY))); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + * @covers Monolog\Handler\FilterHandler::setAcceptedLevels + * @covers Monolog\Handler\FilterHandler::isHandling + */ + public function testHandleProcessOnlyNeededLevels() + { + $test = new TestHandler(); + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE); + + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::NOTICE)); + $this->assertTrue($test->hasNoticeRecords()); + + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertFalse($test->hasWarningRecords()); + $handler->handle($this->getRecord(Logger::ERROR)); + $this->assertFalse($test->hasErrorRecords()); + $handler->handle($this->getRecord(Logger::CRITICAL)); + $this->assertFalse($test->hasCriticalRecords()); + $handler->handle($this->getRecord(Logger::ALERT)); + $this->assertFalse($test->hasAlertRecords()); + $handler->handle($this->getRecord(Logger::EMERGENCY)); + $this->assertFalse($test->hasEmergencyRecords()); + + $test = new TestHandler(); + $handler = new FilterHandler($test, array(Logger::INFO, Logger::ERROR)); + + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::NOTICE)); + $this->assertFalse($test->hasNoticeRecords()); + $handler->handle($this->getRecord(Logger::ERROR)); + $this->assertTrue($test->hasErrorRecords()); + $handler->handle($this->getRecord(Logger::CRITICAL)); + $this->assertFalse($test->hasCriticalRecords()); + } + + /** + * @covers Monolog\Handler\FilterHandler::setAcceptedLevels + * @covers Monolog\Handler\FilterHandler::getAcceptedLevels + */ + public function testAcceptedLevelApi() + { + $test = new TestHandler(); + $handler = new FilterHandler($test); + + $levels = array(Logger::INFO, Logger::ERROR); + $handler->setAcceptedLevels($levels); + $this->assertSame($levels, $handler->getAcceptedLevels()); + + $handler->setAcceptedLevels(array('info', 'error')); + $this->assertSame($levels, $handler->getAcceptedLevels()); + + $levels = array(Logger::CRITICAL, Logger::ALERT, Logger::EMERGENCY); + $handler->setAcceptedLevels(Logger::CRITICAL, Logger::EMERGENCY); + $this->assertSame($levels, $handler->getAcceptedLevels()); + + $handler->setAcceptedLevels('critical', 'emergency'); + $this->assertSame($levels, $handler->getAcceptedLevels()); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new FilterHandler($test, Logger::DEBUG, Logger::EMERGENCY); + $handler->pushProcessor( + function ($record) { + $record['extra']['foo'] = true; + + return $record; + } + ); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + */ + public function testHandleRespectsBubble() + { + $test = new TestHandler(); + + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE, false); + $this->assertTrue($handler->handle($this->getRecord(Logger::INFO))); + $this->assertFalse($handler->handle($this->getRecord(Logger::WARNING))); + + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE, true); + $this->assertFalse($handler->handle($this->getRecord(Logger::INFO))); + $this->assertFalse($handler->handle($this->getRecord(Logger::WARNING))); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + */ + public function testHandleWithCallback() + { + $test = new TestHandler(); + $handler = new FilterHandler( + function ($record, $handler) use ($test) { + return $test; + }, Logger::INFO, Logger::NOTICE, false + ); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + * @expectedException \RuntimeException + */ + public function testHandleWithBadCallbackThrowsException() + { + $handler = new FilterHandler( + function ($record, $handler) { + return 'foo'; + } + ); + $handler->handle($this->getRecord(Logger::WARNING)); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php new file mode 100644 index 00000000..b92bf437 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy; +use Psr\Log\LogLevel; + +class FingersCrossedHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleBuffers() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->close(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 3); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleStopsBufferingAfterTrigger() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + * @covers Monolog\Handler\FingersCrossedHandler::reset + */ + public function testHandleRestartBufferingAfterReset() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->reset(); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleRestartBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::WARNING, 0, false, false); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleBufferLimit() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::WARNING, 2); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleWithCallback() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler(function ($record, $handler) use ($test) { + return $test; + }); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 3); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + * @expectedException RuntimeException + */ + public function testHandleWithBadCallbackThrowsException() + { + $handler = new FingersCrossedHandler(function ($record, $handler) { + return 'foo'; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::isHandling + */ + public function testIsHandlingAlways() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::ERROR); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::isHandlerActivated + */ + public function testErrorLevelActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::isHandlerActivated + */ + public function testErrorLevelActivationStrategyWithPsrLevel() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy('warning')); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testOverrideActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy('warning')); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->activate(); + $this->assertTrue($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::isHandlerActivated + */ + public function testChannelLevelActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ChannelLevelActivationStrategy(Logger::ERROR, array('othertest' => Logger::DEBUG))); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertFalse($test->hasWarningRecords()); + $record = $this->getRecord(Logger::DEBUG); + $record['channel'] = 'othertest'; + $handler->handle($record); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::isHandlerActivated + */ + public function testChannelLevelActivationStrategyWithPsrLevels() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ChannelLevelActivationStrategy('error', array('othertest' => 'debug'))); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertFalse($test->hasWarningRecords()); + $record = $this->getRecord(Logger::DEBUG); + $record['channel'] = 'othertest'; + $handler->handle($record); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::INFO); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::close + */ + public function testPassthruOnClose() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING), 0, true, true, Logger::INFO); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertFalse($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::close + */ + public function testPsrLevelPassthruOnClose() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING), 0, true, true, LogLevel::INFO); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertFalse($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php new file mode 100644 index 00000000..0eb10a63 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\FirePHPHandler + */ +class FirePHPHandlerTest extends TestCase +{ + public function setUp() + { + TestFirePHPHandler::reset(); + $_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; FirePHP/1.0'; + } + + public function testHeaders() + { + $handler = new TestFirePHPHandler; + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2', + 'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1', + 'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3', + 'X-Wf-1-1-1-1' => 'test', + 'X-Wf-1-1-1-2' => 'test', + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testConcurrentHandlers() + { + $handler = new TestFirePHPHandler; + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $handler2 = new TestFirePHPHandler; + $handler2->setFormatter($this->getIdentityFormatter()); + $handler2->handle($this->getRecord(Logger::DEBUG)); + $handler2->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2', + 'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1', + 'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3', + 'X-Wf-1-1-1-1' => 'test', + 'X-Wf-1-1-1-2' => 'test', + ); + + $expected2 = array( + 'X-Wf-1-1-1-3' => 'test', + 'X-Wf-1-1-1-4' => 'test', + ); + + $this->assertEquals($expected, $handler->getHeaders()); + $this->assertEquals($expected2, $handler2->getHeaders()); + } +} + +class TestFirePHPHandler extends FirePHPHandler +{ + protected $headers = array(); + + public static function reset() + { + self::$initialized = false; + self::$sendHeaders = true; + self::$messageIndex = 1; + } + + protected function sendHeader($header, $content) + { + $this->headers[$header] = $content; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep b/vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php new file mode 100644 index 00000000..91cdd312 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.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 Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; +use Monolog\TestCase; + +/** + * @coversDefaultClass \Monolog\Handler\FleepHookHandler + */ +class FleepHookHandlerTest extends TestCase +{ + /** + * Default token to use in tests + */ + const TOKEN = '123abc'; + + /** + * @var FleepHookHandler + */ + private $handler; + + public function setUp() + { + parent::setUp(); + + if (!extension_loaded('openssl')) { + $this->markTestSkipped('This test requires openssl extension to run'); + } + + // Create instances of the handler and logger for convenience + $this->handler = new FleepHookHandler(self::TOKEN); + } + + /** + * @covers ::__construct + */ + public function testConstructorSetsExpectedDefaults() + { + $this->assertEquals(Logger::DEBUG, $this->handler->getLevel()); + $this->assertEquals(true, $this->handler->getBubble()); + } + + /** + * @covers ::getDefaultFormatter + */ + public function testHandlerUsesLineFormatterWhichIgnoresEmptyArrays() + { + $record = array( + 'message' => 'msg', + 'context' => array(), + 'level' => Logger::DEBUG, + 'level_name' => Logger::getLevelName(Logger::DEBUG), + 'channel' => 'channel', + 'datetime' => new \DateTime(), + 'extra' => array(), + ); + + $expectedFormatter = new LineFormatter(null, null, true, true); + $expected = $expectedFormatter->format($record); + + $handlerFormatter = $this->handler->getFormatter(); + $actual = $handlerFormatter->format($record); + + $this->assertEquals($expected, $actual, 'Empty context and extra arrays should not be rendered'); + } + + /** + * @covers ::__construct + */ + public function testConnectionStringisConstructedCorrectly() + { + $this->assertEquals('ssl://' . FleepHookHandler::FLEEP_HOST . ':443', $this->handler->getConnectionString()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php new file mode 100644 index 00000000..4b120d51 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.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 Monolog\Handler; + +use Monolog\Formatter\FlowdockFormatter; +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Dominik Liebler + * @see https://www.hipchat.com/docs/api + */ +class FlowdockHandlerTest extends TestCase +{ + /** + * @var resource + */ + private $res; + + /** + * @var FlowdockHandler + */ + private $handler; + + public function setUp() + { + if (!extension_loaded('openssl')) { + $this->markTestSkipped('This test requires openssl to run'); + } + } + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v1\/messages\/team_inbox\/.* HTTP\/1.1\\r\\nHost: api.flowdock.com\\r\\nContent-Type: application\/json\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/"source":"test_source"/', $content); + $this->assertRegexp('/"from_address":"source@test\.com"/', $content); + } + + private function createHandler($token = 'myToken') + { + $constructorArgs = array($token, Logger::DEBUG); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\FlowdockHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter(new FlowdockFormatter('test_source', 'source@test.com')); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.php new file mode 100644 index 00000000..9d007b13 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.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 Monolog\Handler; + +use Gelf\Message; +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +class GelfHandlerLegacyTest extends TestCase +{ + public function setUp() + { + if (!class_exists('Gelf\MessagePublisher') || !class_exists('Gelf\Message')) { + $this->markTestSkipped("mlehner/gelf-php not installed"); + } + + require_once __DIR__ . '/GelfMockMessagePublisher.php'; + } + + /** + * @covers Monolog\Handler\GelfHandler::__construct + */ + public function testConstruct() + { + $handler = new GelfHandler($this->getMessagePublisher()); + $this->assertInstanceOf('Monolog\Handler\GelfHandler', $handler); + } + + protected function getHandler($messagePublisher) + { + $handler = new GelfHandler($messagePublisher); + + return $handler; + } + + protected function getMessagePublisher() + { + return new GelfMockMessagePublisher('localhost'); + } + + public function testDebug() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $record = $this->getRecord(Logger::DEBUG, "A test debug message"); + $handler->handle($record); + + $this->assertEquals(7, $messagePublisher->lastMessage->getLevel()); + $this->assertEquals('test', $messagePublisher->lastMessage->getFacility()); + $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage()); + $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage()); + } + + public function testWarning() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $handler->handle($record); + + $this->assertEquals(4, $messagePublisher->lastMessage->getLevel()); + $this->assertEquals('test', $messagePublisher->lastMessage->getFacility()); + $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage()); + $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage()); + } + + public function testInjectedGelfMessageFormatter() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $handler->setFormatter(new GelfMessageFormatter('mysystem', 'EXT', 'CTX')); + + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $record['extra']['blarg'] = 'yep'; + $record['context']['from'] = 'logger'; + $handler->handle($record); + + $this->assertEquals('mysystem', $messagePublisher->lastMessage->getHost()); + $this->assertArrayHasKey('_EXTblarg', $messagePublisher->lastMessage->toArray()); + $this->assertArrayHasKey('_CTXfrom', $messagePublisher->lastMessage->toArray()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php new file mode 100644 index 00000000..8cdd64f4 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\Message; +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +class GelfHandlerTest extends TestCase +{ + public function setUp() + { + if (!class_exists('Gelf\Publisher') || !class_exists('Gelf\Message')) { + $this->markTestSkipped("graylog2/gelf-php not installed"); + } + } + + /** + * @covers Monolog\Handler\GelfHandler::__construct + */ + public function testConstruct() + { + $handler = new GelfHandler($this->getMessagePublisher()); + $this->assertInstanceOf('Monolog\Handler\GelfHandler', $handler); + } + + protected function getHandler($messagePublisher) + { + $handler = new GelfHandler($messagePublisher); + + return $handler; + } + + protected function getMessagePublisher() + { + return $this->getMock('Gelf\Publisher', array('publish'), array(), '', false); + } + + public function testDebug() + { + $record = $this->getRecord(Logger::DEBUG, "A test debug message"); + $expectedMessage = new Message(); + $expectedMessage + ->setLevel(7) + ->setFacility("test") + ->setShortMessage($record['message']) + ->setTimestamp($record['datetime']) + ; + + $messagePublisher = $this->getMessagePublisher(); + $messagePublisher->expects($this->once()) + ->method('publish') + ->with($expectedMessage); + + $handler = $this->getHandler($messagePublisher); + + $handler->handle($record); + } + + public function testWarning() + { + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $expectedMessage = new Message(); + $expectedMessage + ->setLevel(4) + ->setFacility("test") + ->setShortMessage($record['message']) + ->setTimestamp($record['datetime']) + ; + + $messagePublisher = $this->getMessagePublisher(); + $messagePublisher->expects($this->once()) + ->method('publish') + ->with($expectedMessage); + + $handler = $this->getHandler($messagePublisher); + + $handler->handle($record); + } + + public function testInjectedGelfMessageFormatter() + { + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $record['extra']['blarg'] = 'yep'; + $record['context']['from'] = 'logger'; + + $expectedMessage = new Message(); + $expectedMessage + ->setLevel(4) + ->setFacility("test") + ->setHost("mysystem") + ->setShortMessage($record['message']) + ->setTimestamp($record['datetime']) + ->setAdditional("EXTblarg", 'yep') + ->setAdditional("CTXfrom", 'logger') + ; + + $messagePublisher = $this->getMessagePublisher(); + $messagePublisher->expects($this->once()) + ->method('publish') + ->with($expectedMessage); + + $handler = $this->getHandler($messagePublisher); + $handler->setFormatter(new GelfMessageFormatter('mysystem', 'EXT', 'CTX')); + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.php b/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.php new file mode 100644 index 00000000..873d92fb --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.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 Monolog\Handler; + +use Gelf\MessagePublisher; +use Gelf\Message; + +class GelfMockMessagePublisher extends MessagePublisher +{ + public function publish(Message $message) + { + $this->lastMessage = $message; + } + + public $lastMessage = null; +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php new file mode 100644 index 00000000..a1b86176 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class GroupHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\GroupHandler::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorOnlyTakesHandler() + { + new GroupHandler(array(new TestHandler(), "foo")); + } + + /** + * @covers Monolog\Handler\GroupHandler::__construct + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandle() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\GroupHandler::handleBatch + */ + public function testHandleBatch() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\GroupHandler::isHandling + */ + public function testIsHandling() + { + $testHandlers = array(new TestHandler(Logger::ERROR), new TestHandler(Logger::WARNING)); + $handler = new GroupHandler($testHandlers); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::ERROR))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::WARNING))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new GroupHandler(array($test)); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandleBatchUsesProcessors() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + $this->assertTrue($records[1]['extra']['foo']); + } + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php new file mode 100644 index 00000000..d8d0452c --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +/** + * @author Alexey Karapetov + */ +class HandlerWrapperTest extends TestCase +{ + /** + * @var HandlerWrapper + */ + private $wrapper; + + private $handler; + + public function setUp() + { + parent::setUp(); + $this->handler = $this->getMock('Monolog\\Handler\\HandlerInterface'); + $this->wrapper = new HandlerWrapper($this->handler); + } + + /** + * @return array + */ + public function trueFalseDataProvider() + { + return array( + array(true), + array(false), + ); + } + + /** + * @param $result + * @dataProvider trueFalseDataProvider + */ + public function testIsHandling($result) + { + $record = $this->getRecord(); + $this->handler->expects($this->once()) + ->method('isHandling') + ->with($record) + ->willReturn($result); + + $this->assertEquals($result, $this->wrapper->isHandling($record)); + } + + /** + * @param $result + * @dataProvider trueFalseDataProvider + */ + public function testHandle($result) + { + $record = $this->getRecord(); + $this->handler->expects($this->once()) + ->method('handle') + ->with($record) + ->willReturn($result); + + $this->assertEquals($result, $this->wrapper->handle($record)); + } + + /** + * @param $result + * @dataProvider trueFalseDataProvider + */ + public function testHandleBatch($result) + { + $records = $this->getMultipleRecords(); + $this->handler->expects($this->once()) + ->method('handleBatch') + ->with($records) + ->willReturn($result); + + $this->assertEquals($result, $this->wrapper->handleBatch($records)); + } + + public function testPushProcessor() + { + $processor = function () {}; + $this->handler->expects($this->once()) + ->method('pushProcessor') + ->with($processor); + + $this->assertEquals($this->wrapper, $this->wrapper->pushProcessor($processor)); + } + + public function testPopProcessor() + { + $processor = function () {}; + $this->handler->expects($this->once()) + ->method('popProcessor') + ->willReturn($processor); + + $this->assertEquals($processor, $this->wrapper->popProcessor()); + } + + public function testSetFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $this->handler->expects($this->once()) + ->method('setFormatter') + ->with($formatter); + + $this->assertEquals($this->wrapper, $this->wrapper->setFormatter($formatter)); + } + + public function testGetFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $this->handler->expects($this->once()) + ->method('getFormatter') + ->willReturn($formatter); + + $this->assertEquals($formatter, $this->wrapper->getFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php new file mode 100644 index 00000000..52dc9dac --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Rafael Dohms + * @see https://www.hipchat.com/docs/api + */ +class HipChatHandlerTest extends TestCase +{ + private $res; + /** @var HipChatHandler */ + private $handler; + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v1\/rooms\/message\?format=json&auth_token=.* HTTP\/1.1\\r\\nHost: api.hipchat.com\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testWriteCustomHostHeader() + { + $this->createHandler('myToken', 'room1', 'Monolog', true, 'hipchat.foo.bar'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v1\/rooms\/message\?format=json&auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testWriteV2() + { + $this->createHandler('myToken', 'room1', 'Monolog', false, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v2\/room\/room1\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testWriteV2Notify() + { + $this->createHandler('myToken', 'room1', 'Monolog', true, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v2\/room\/room1\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testRoomSpaces() + { + $this->createHandler('myToken', 'room name', 'Monolog', false, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v2\/room\/room%20name\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/notify=0&message=test1&message_format=text&color=red&room_id=room1&from=Monolog$/', $content); + } + + public function testWriteContentV1WithoutName() + { + $this->createHandler('myToken', 'room1', null, false, 'hipchat.foo.bar', 'v1'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/notify=0&message=test1&message_format=text&color=red&room_id=room1&from=$/', $content); + + return $content; + } + + /** + * @depends testWriteCustomHostHeader + */ + public function testWriteContentNotify($content) + { + $this->assertRegexp('/notify=1&message=test1&message_format=text&color=red&room_id=room1&from=Monolog$/', $content); + } + + /** + * @depends testWriteV2 + */ + public function testWriteContentV2($content) + { + $this->assertRegexp('/notify=false&message=test1&message_format=text&color=red&from=Monolog$/', $content); + } + + /** + * @depends testWriteV2Notify + */ + public function testWriteContentV2Notify($content) + { + $this->assertRegexp('/notify=true&message=test1&message_format=text&color=red&from=Monolog$/', $content); + } + + public function testWriteContentV2WithoutName() + { + $this->createHandler('myToken', 'room1', null, false, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/notify=false&message=test1&message_format=text&color=red$/', $content); + + return $content; + } + + public function testWriteWithComplexMessage() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content); + } + + public function testWriteTruncatesLongMessage() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, str_repeat('abcde', 2000))); + fseek($this->res, 0); + $content = fread($this->res, 12000); + + $this->assertRegexp('/message='.str_repeat('abcde', 1900).'\+%5Btruncated%5D/', $content); + } + + /** + * @dataProvider provideLevelColors + */ + public function testWriteWithErrorLevelsAndColors($level, $expectedColor) + { + $this->createHandler(); + $this->handler->handle($this->getRecord($level, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/color='.$expectedColor.'/', $content); + } + + public function provideLevelColors() + { + return array( + array(Logger::DEBUG, 'gray'), + array(Logger::INFO, 'green'), + array(Logger::WARNING, 'yellow'), + array(Logger::ERROR, 'red'), + array(Logger::CRITICAL, 'red'), + array(Logger::ALERT, 'red'), + array(Logger::EMERGENCY,'red'), + array(Logger::NOTICE, 'green'), + ); + } + + /** + * @dataProvider provideBatchRecords + */ + public function testHandleBatch($records, $expectedColor) + { + $this->createHandler(); + + $this->handler->handleBatch($records); + + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/color='.$expectedColor.'/', $content); + } + + public function provideBatchRecords() + { + return array( + array( + array( + array('level' => Logger::WARNING, 'message' => 'Oh bugger!', 'level_name' => 'warning', 'datetime' => new \DateTime()), + array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()), + array('level' => Logger::CRITICAL, 'message' => 'Everything is broken!', 'level_name' => 'critical', 'datetime' => new \DateTime()), + ), + 'red', + ), + array( + array( + array('level' => Logger::WARNING, 'message' => 'Oh bugger!', 'level_name' => 'warning', 'datetime' => new \DateTime()), + array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()), + ), + 'yellow', + ), + array( + array( + array('level' => Logger::DEBUG, 'message' => 'Just debugging.', 'level_name' => 'debug', 'datetime' => new \DateTime()), + array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()), + ), + 'green', + ), + array( + array( + array('level' => Logger::DEBUG, 'message' => 'Just debugging.', 'level_name' => 'debug', 'datetime' => new \DateTime()), + ), + 'gray', + ), + ); + } + + private function createHandler($token = 'myToken', $room = 'room1', $name = 'Monolog', $notify = false, $host = 'api.hipchat.com', $version = 'v1') + { + $constructorArgs = array($token, $room, $name, $notify, Logger::DEBUG, true, true, 'text', $host, $version); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\HipChatHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testCreateWithTooLongName() + { + $hipChatHandler = new HipChatHandler('token', 'room', 'SixteenCharsHere'); + } + + public function testCreateWithTooLongNameV2() + { + // creating a handler with too long of a name but using the v2 api doesn't matter. + $hipChatHandler = new HipChatHandler('token', 'room', 'SixteenCharsHere', false, Logger::CRITICAL, true, true, 'test', 'api.hipchat.com', 'v2'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php new file mode 100644 index 00000000..b2deb40a --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Robert Kaufmann III + */ +class LogEntriesHandlerTest extends TestCase +{ + /** + * @var resource + */ + private $res; + + /** + * @var LogEntriesHandler + */ + private $handler; + + public function testWriteContent() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Critical write test')); + + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/testToken \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] test.CRITICAL: Critical write test/', $content); + } + + public function testWriteBatchContent() + { + $records = array( + $this->getRecord(), + $this->getRecord(), + $this->getRecord(), + ); + $this->createHandler(); + $this->handler->handleBatch($records); + + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/(testToken \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] .* \[\] \[\]\n){3}/', $content); + } + + private function createHandler() + { + $useSSL = extension_loaded('openssl'); + $args = array('testToken', $useSSL, Logger::DEBUG, true); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\LogEntriesHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $args + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php new file mode 100644 index 00000000..6754f3d6 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.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 Monolog\Handler; + +use Monolog\Logger; +use Monolog\TestCase; + +class MailHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\MailHandler::handleBatch + */ + public function testHandleBatch() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once()) + ->method('formatBatch'); // Each record is formatted + + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + $handler->expects($this->once()) + ->method('send'); + $handler->expects($this->never()) + ->method('write'); // write is for individual records + + $handler->setFormatter($formatter); + + $handler->handleBatch($this->getMultipleRecords()); + } + + /** + * @covers Monolog\Handler\MailHandler::handleBatch + */ + public function testHandleBatchNotSendsMailIfMessagesAreBelowLevel() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + ); + + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + $handler->expects($this->never()) + ->method('send'); + $handler->setLevel(Logger::ERROR); + + $handler->handleBatch($records); + } + + /** + * @covers Monolog\Handler\MailHandler::write + */ + public function testHandle() + { + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + + $record = $this->getRecord(); + $records = array($record); + $records[0]['formatted'] = '['.$record['datetime']->format('Y-m-d H:i:s').'] test.WARNING: test [] []'."\n"; + + $handler->expects($this->once()) + ->method('send') + ->with($records[0]['formatted'], $records); + + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php b/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php new file mode 100644 index 00000000..a0833225 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.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 Monolog\Handler; + +use Raven_Client; + +class MockRavenClient extends Raven_Client +{ + public function capture($data, $stack, $vars = null) + { + $data = array_merge($this->get_user_data(), $data); + $this->lastData = $data; + $this->lastStack = $stack; + } + + public $lastData; + public $lastStack; +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php new file mode 100644 index 00000000..0fdef63a --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class MongoDBHandlerTest extends TestCase +{ + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForInvalidMongo() + { + new MongoDBHandler(new \stdClass(), 'DB', 'Collection'); + } + + public function testHandle() + { + $mongo = $this->getMock('Mongo', array('selectCollection'), array(), '', false); + $collection = $this->getMock('stdClass', array('save')); + + $mongo->expects($this->once()) + ->method('selectCollection') + ->with('DB', 'Collection') + ->will($this->returnValue($collection)); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + 'message' => 'test', + 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34), + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'datetime' => $record['datetime']->format('Y-m-d H:i:s'), + 'extra' => array(), + ); + + $collection->expects($this->once()) + ->method('save') + ->with($expected); + + $handler = new MongoDBHandler($mongo, 'DB', 'Collection'); + $handler->handle($record); + } +} + +if (!class_exists('Mongo')) { + class Mongo + { + public function selectCollection() + { + } + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php new file mode 100644 index 00000000..ddf545db --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use InvalidArgumentException; + +function mail($to, $subject, $message, $additional_headers = null, $additional_parameters = null) +{ + $GLOBALS['mail'][] = func_get_args(); +} + +class NativeMailerHandlerTest extends TestCase +{ + protected function setUp() + { + $GLOBALS['mail'] = array(); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', "receiver@example.org\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->addHeader("Content-Type: text/html\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterArrayHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->addHeader(array("Content-Type: text/html\r\nFrom: faked@attacker.org")); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterContentTypeInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->setContentType("text/html\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterEncodingInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->setEncoding("utf-8\r\nFrom: faked@attacker.org"); + } + + public function testSend() + { + $to = 'spammer@example.org'; + $subject = 'dear victim'; + $from = 'receiver@example.org'; + + $mailer = new NativeMailerHandler($to, $subject, $from); + $mailer->handleBatch(array()); + + // batch is empty, nothing sent + $this->assertEmpty($GLOBALS['mail']); + + // non-empty batch + $mailer->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + $this->assertNotEmpty($GLOBALS['mail']); + $this->assertInternalType('array', $GLOBALS['mail']); + $this->assertArrayHasKey('0', $GLOBALS['mail']); + $params = $GLOBALS['mail'][0]; + $this->assertCount(5, $params); + $this->assertSame($to, $params[0]); + $this->assertSame($subject, $params[1]); + $this->assertStringEndsWith(" test.ERROR: Foo Bar Baz [] []\n", $params[2]); + $this->assertSame("From: $from\r\nContent-type: text/plain; charset=utf-8\r\n", $params[3]); + $this->assertSame('', $params[4]); + } + + public function testMessageSubjectFormatting() + { + $mailer = new NativeMailerHandler('to@example.org', 'Alert: %level_name% %message%', 'from@example.org'); + $mailer->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + $this->assertNotEmpty($GLOBALS['mail']); + $this->assertInternalType('array', $GLOBALS['mail']); + $this->assertArrayHasKey('0', $GLOBALS['mail']); + $params = $GLOBALS['mail'][0]; + $this->assertCount(5, $params); + $this->assertSame('Alert: ERROR Foo Bar Baz', $params[1]); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php new file mode 100644 index 00000000..4d3a615f --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\TestCase; +use Monolog\Logger; + +class NewRelicHandlerTest extends TestCase +{ + public static $appname; + public static $customParameters; + public static $transactionName; + + public function setUp() + { + self::$appname = null; + self::$customParameters = array(); + self::$transactionName = null; + } + + /** + * @expectedException Monolog\Handler\MissingExtensionException + */ + public function testThehandlerThrowsAnExceptionIfTheNRExtensionIsNotLoaded() + { + $handler = new StubNewRelicHandlerWithoutExtension(); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testThehandlerCanHandleTheRecord() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testThehandlerCanAddContextParamsToTheNewRelicTrace() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('a' => 'b'))); + $this->assertEquals(array('context_a' => 'b'), self::$customParameters); + } + + public function testThehandlerCanAddExplodedContextParamsToTheNewRelicTrace() + { + $handler = new StubNewRelicHandler(Logger::ERROR, true, self::$appname, true); + $handler->handle($this->getRecord( + Logger::ERROR, + 'log message', + array('a' => array('key1' => 'value1', 'key2' => 'value2')) + )); + $this->assertEquals( + array('context_a_key1' => 'value1', 'context_a_key2' => 'value2'), + self::$customParameters + ); + } + + public function testThehandlerCanAddExtraParamsToTheNewRelicTrace() + { + $record = $this->getRecord(Logger::ERROR, 'log message'); + $record['extra'] = array('c' => 'd'); + + $handler = new StubNewRelicHandler(); + $handler->handle($record); + + $this->assertEquals(array('extra_c' => 'd'), self::$customParameters); + } + + public function testThehandlerCanAddExplodedExtraParamsToTheNewRelicTrace() + { + $record = $this->getRecord(Logger::ERROR, 'log message'); + $record['extra'] = array('c' => array('key1' => 'value1', 'key2' => 'value2')); + + $handler = new StubNewRelicHandler(Logger::ERROR, true, self::$appname, true); + $handler->handle($record); + + $this->assertEquals( + array('extra_c_key1' => 'value1', 'extra_c_key2' => 'value2'), + self::$customParameters + ); + } + + public function testThehandlerCanAddExtraContextAndParamsToTheNewRelicTrace() + { + $record = $this->getRecord(Logger::ERROR, 'log message', array('a' => 'b')); + $record['extra'] = array('c' => 'd'); + + $handler = new StubNewRelicHandler(); + $handler->handle($record); + + $expected = array( + 'context_a' => 'b', + 'extra_c' => 'd', + ); + + $this->assertEquals($expected, self::$customParameters); + } + + public function testThehandlerCanHandleTheRecordsFormattedUsingTheLineFormatter() + { + $handler = new StubNewRelicHandler(); + $handler->setFormatter(new LineFormatter()); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testTheAppNameIsNullByDefault() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals(null, self::$appname); + } + + public function testTheAppNameCanBeInjectedFromtheConstructor() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, 'myAppName'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals('myAppName', self::$appname); + } + + public function testTheAppNameCanBeOverriddenFromEachLog() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, 'myAppName'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('appname' => 'logAppName'))); + + $this->assertEquals('logAppName', self::$appname); + } + + public function testTheTransactionNameIsNullByDefault() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals(null, self::$transactionName); + } + + public function testTheTransactionNameCanBeInjectedFromTheConstructor() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, null, false, 'myTransaction'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals('myTransaction', self::$transactionName); + } + + public function testTheTransactionNameCanBeOverriddenFromEachLog() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, null, false, 'myTransaction'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('transaction_name' => 'logTransactName'))); + + $this->assertEquals('logTransactName', self::$transactionName); + } +} + +class StubNewRelicHandlerWithoutExtension extends NewRelicHandler +{ + protected function isNewRelicEnabled() + { + return false; + } +} + +class StubNewRelicHandler extends NewRelicHandler +{ + protected function isNewRelicEnabled() + { + return true; + } +} + +function newrelic_notice_error() +{ + return true; +} + +function newrelic_set_appname($appname) +{ + return NewRelicHandlerTest::$appname = $appname; +} + +function newrelic_name_transaction($transactionName) +{ + return NewRelicHandlerTest::$transactionName = $transactionName; +} + +function newrelic_add_custom_parameter($key, $value) +{ + NewRelicHandlerTest::$customParameters[$key] = $value; + + return true; +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php new file mode 100644 index 00000000..292df78c --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\NullHandler::handle + */ +class NullHandlerTest extends TestCase +{ + public function testHandle() + { + $handler = new NullHandler(); + $this->assertTrue($handler->handle($this->getRecord())); + } + + public function testHandleLowerLevelRecord() + { + $handler = new NullHandler(Logger::WARNING); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php new file mode 100644 index 00000000..152573ef --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php @@ -0,0 +1,273 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Exception; +use Monolog\ErrorHandler; +use Monolog\Logger; +use Monolog\TestCase; +use PhpConsole\Connector; +use PhpConsole\Dispatcher\Debug as DebugDispatcher; +use PhpConsole\Dispatcher\Errors as ErrorDispatcher; +use PhpConsole\Handler; +use PHPUnit_Framework_MockObject_MockObject; + +/** + * @covers Monolog\Handler\PHPConsoleHandler + * @author Sergey Barbushin https://www.linkedin.com/in/barbushin + */ +class PHPConsoleHandlerTest extends TestCase +{ + /** @var Connector|PHPUnit_Framework_MockObject_MockObject */ + protected $connector; + /** @var DebugDispatcher|PHPUnit_Framework_MockObject_MockObject */ + protected $debugDispatcher; + /** @var ErrorDispatcher|PHPUnit_Framework_MockObject_MockObject */ + protected $errorDispatcher; + + protected function setUp() + { + if (!class_exists('PhpConsole\Connector')) { + $this->markTestSkipped('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + } + $this->connector = $this->initConnectorMock(); + + $this->debugDispatcher = $this->initDebugDispatcherMock($this->connector); + $this->connector->setDebugDispatcher($this->debugDispatcher); + + $this->errorDispatcher = $this->initErrorDispatcherMock($this->connector); + $this->connector->setErrorsDispatcher($this->errorDispatcher); + } + + protected function initDebugDispatcherMock(Connector $connector) + { + return $this->getMockBuilder('PhpConsole\Dispatcher\Debug') + ->disableOriginalConstructor() + ->setMethods(array('dispatchDebug')) + ->setConstructorArgs(array($connector, $connector->getDumper())) + ->getMock(); + } + + protected function initErrorDispatcherMock(Connector $connector) + { + return $this->getMockBuilder('PhpConsole\Dispatcher\Errors') + ->disableOriginalConstructor() + ->setMethods(array('dispatchError', 'dispatchException')) + ->setConstructorArgs(array($connector, $connector->getDumper())) + ->getMock(); + } + + protected function initConnectorMock() + { + $connector = $this->getMockBuilder('PhpConsole\Connector') + ->disableOriginalConstructor() + ->setMethods(array( + 'sendMessage', + 'onShutDown', + 'isActiveClient', + 'setSourcesBasePath', + 'setServerEncoding', + 'setPassword', + 'enableSslOnlyMode', + 'setAllowedIpMasks', + 'setHeadersLimit', + 'startEvalRequestsListener', + )) + ->getMock(); + + $connector->expects($this->any()) + ->method('isActiveClient') + ->will($this->returnValue(true)); + + return $connector; + } + + protected function getHandlerDefaultOption($name) + { + $handler = new PHPConsoleHandler(array(), $this->connector); + $options = $handler->getOptions(); + + return $options[$name]; + } + + protected function initLogger($handlerOptions = array(), $level = Logger::DEBUG) + { + return new Logger('test', array( + new PHPConsoleHandler($handlerOptions, $this->connector, $level), + )); + } + + public function testInitWithDefaultConnector() + { + $handler = new PHPConsoleHandler(); + $this->assertEquals(spl_object_hash(Connector::getInstance()), spl_object_hash($handler->getConnector())); + } + + public function testInitWithCustomConnector() + { + $handler = new PHPConsoleHandler(array(), $this->connector); + $this->assertEquals(spl_object_hash($this->connector), spl_object_hash($handler->getConnector())); + } + + public function testDebug() + { + $this->debugDispatcher->expects($this->once())->method('dispatchDebug')->with($this->equalTo('test')); + $this->initLogger()->addDebug('test'); + } + + public function testDebugContextInMessage() + { + $message = 'test'; + $tag = 'tag'; + $context = array($tag, 'custom' => mt_rand()); + $expectedMessage = $message . ' ' . json_encode(array_slice($context, 1)); + $this->debugDispatcher->expects($this->once())->method('dispatchDebug')->with( + $this->equalTo($expectedMessage), + $this->equalTo($tag) + ); + $this->initLogger()->addDebug($message, $context); + } + + public function testDebugTags($tagsContextKeys = null) + { + $expectedTags = mt_rand(); + $logger = $this->initLogger($tagsContextKeys ? array('debugTagsKeysInContext' => $tagsContextKeys) : array()); + if (!$tagsContextKeys) { + $tagsContextKeys = $this->getHandlerDefaultOption('debugTagsKeysInContext'); + } + foreach ($tagsContextKeys as $key) { + $debugDispatcher = $this->initDebugDispatcherMock($this->connector); + $debugDispatcher->expects($this->once())->method('dispatchDebug')->with( + $this->anything(), + $this->equalTo($expectedTags) + ); + $this->connector->setDebugDispatcher($debugDispatcher); + $logger->addDebug('test', array($key => $expectedTags)); + } + } + + public function testError($classesPartialsTraceIgnore = null) + { + $code = E_USER_NOTICE; + $message = 'message'; + $file = __FILE__; + $line = __LINE__; + $this->errorDispatcher->expects($this->once())->method('dispatchError')->with( + $this->equalTo($code), + $this->equalTo($message), + $this->equalTo($file), + $this->equalTo($line), + $classesPartialsTraceIgnore ?: $this->equalTo($this->getHandlerDefaultOption('classesPartialsTraceIgnore')) + ); + $errorHandler = ErrorHandler::register($this->initLogger($classesPartialsTraceIgnore ? array('classesPartialsTraceIgnore' => $classesPartialsTraceIgnore) : array()), false); + $errorHandler->registerErrorHandler(array(), false, E_USER_WARNING); + $errorHandler->handleError($code, $message, $file, $line); + } + + public function testException() + { + $e = new Exception(); + $this->errorDispatcher->expects($this->once())->method('dispatchException')->with( + $this->equalTo($e) + ); + $handler = $this->initLogger(); + $handler->log( + \Psr\Log\LogLevel::ERROR, + sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()), + array('exception' => $e) + ); + } + + /** + * @expectedException Exception + */ + public function testWrongOptionsThrowsException() + { + new PHPConsoleHandler(array('xxx' => 1)); + } + + public function testOptionEnabled() + { + $this->debugDispatcher->expects($this->never())->method('dispatchDebug'); + $this->initLogger(array('enabled' => false))->addDebug('test'); + } + + public function testOptionClassesPartialsTraceIgnore() + { + $this->testError(array('Class', 'Namespace\\')); + } + + public function testOptionDebugTagsKeysInContext() + { + $this->testDebugTags(array('key1', 'key2')); + } + + public function testOptionUseOwnErrorsAndExceptionsHandler() + { + $this->initLogger(array('useOwnErrorsHandler' => true, 'useOwnExceptionsHandler' => true)); + $this->assertEquals(array(Handler::getInstance(), 'handleError'), set_error_handler(function () { + })); + $this->assertEquals(array(Handler::getInstance(), 'handleException'), set_exception_handler(function () { + })); + } + + public static function provideConnectorMethodsOptionsSets() + { + return array( + array('sourcesBasePath', 'setSourcesBasePath', __DIR__), + array('serverEncoding', 'setServerEncoding', 'cp1251'), + array('password', 'setPassword', '******'), + array('enableSslOnlyMode', 'enableSslOnlyMode', true, false), + array('ipMasks', 'setAllowedIpMasks', array('127.0.0.*')), + array('headersLimit', 'setHeadersLimit', 2500), + array('enableEvalListener', 'startEvalRequestsListener', true, false), + ); + } + + /** + * @dataProvider provideConnectorMethodsOptionsSets + */ + public function testOptionCallsConnectorMethod($option, $method, $value, $isArgument = true) + { + $expectCall = $this->connector->expects($this->once())->method($method); + if ($isArgument) { + $expectCall->with($value); + } + new PHPConsoleHandler(array($option => $value), $this->connector); + } + + public function testOptionDetectDumpTraceAndSource() + { + new PHPConsoleHandler(array('detectDumpTraceAndSource' => true), $this->connector); + $this->assertTrue($this->connector->getDebugDispatcher()->detectTraceAndSource); + } + + public static function provideDumperOptionsValues() + { + return array( + array('dumperLevelLimit', 'levelLimit', 1001), + array('dumperItemsCountLimit', 'itemsCountLimit', 1002), + array('dumperItemSizeLimit', 'itemSizeLimit', 1003), + array('dumperDumpSizeLimit', 'dumpSizeLimit', 1004), + array('dumperDetectCallbacks', 'detectCallbacks', true), + ); + } + + /** + * @dataProvider provideDumperOptionsValues + */ + public function testDumperOptions($option, $dumperProperty, $value) + { + new PHPConsoleHandler(array($option => $value), $this->connector); + $this->assertEquals($value, $this->connector->getDumper()->$dumperProperty); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php new file mode 100644 index 00000000..64eaab16 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\PsrHandler::handle + */ +class PsrHandlerTest extends TestCase +{ + public function logLevelProvider() + { + $levels = array(); + $monologLogger = new Logger(''); + + foreach ($monologLogger->getLevels() as $levelName => $level) { + $levels[] = array($levelName, $level); + } + + return $levels; + } + + /** + * @dataProvider logLevelProvider + */ + public function testHandlesAllLevels($levelName, $level) + { + $message = 'Hello, world! ' . $level; + $context = array('foo' => 'bar', 'level' => $level); + + $psrLogger = $this->getMock('Psr\Log\NullLogger'); + $psrLogger->expects($this->once()) + ->method('log') + ->with(strtolower($levelName), $message, $context); + + $handler = new PsrHandler($psrLogger); + $handler->handle(array('level' => $level, 'level_name' => $levelName, 'message' => $message, 'context' => $context)); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php new file mode 100644 index 00000000..56df474a --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * Almost all examples (expected header, titles, messages) taken from + * https://www.pushover.net/api + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + */ +class PushoverHandlerTest extends TestCase +{ + private $res; + private $handler; + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/1\/messages.json HTTP\/1.1\\r\\nHost: api.pushover.net\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}$/', $content); + } + + public function testWriteWithComplexTitle() + { + $this->createHandler('myToken', 'myUser', 'Backup finished - SQL1'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/title=Backup\+finished\+-\+SQL1/', $content); + } + + public function testWriteWithComplexMessage() + { + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content); + } + + public function testWriteWithTooLongMessage() + { + $message = str_pad('test', 520, 'a'); + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, $message)); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $expectedMessage = substr($message, 0, 505); + + $this->assertRegexp('/message=' . $expectedMessage . '&title/', $content); + } + + public function testWriteWithHighPriority() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}&priority=1$/', $content); + } + + public function testWriteWithEmergencyPriority() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200$/', $content); + } + + public function testWriteToMultipleUsers() + { + $this->createHandler('myToken', array('userA', 'userB')); + $this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=userA&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200POST/', $content); + $this->assertRegexp('/token=myToken&user=userB&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200$/', $content); + } + + private function createHandler($token = 'myToken', $user = 'myUser', $title = 'Monolog') + { + $constructorArgs = array($token, $user, $title); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\PushoverHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php new file mode 100644 index 00000000..26d212b9 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php @@ -0,0 +1,255 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +class RavenHandlerTest extends TestCase +{ + public function setUp() + { + if (!class_exists('Raven_Client')) { + $this->markTestSkipped('raven/raven not installed'); + } + + require_once __DIR__ . '/MockRavenClient.php'; + } + + /** + * @covers Monolog\Handler\RavenHandler::__construct + */ + public function testConstruct() + { + $handler = new RavenHandler($this->getRavenClient()); + $this->assertInstanceOf('Monolog\Handler\RavenHandler', $handler); + } + + protected function getHandler($ravenClient) + { + $handler = new RavenHandler($ravenClient); + + return $handler; + } + + protected function getRavenClient() + { + $dsn = 'http://43f6017361224d098402974103bfc53d:a6a0538fc2934ba2bed32e08741b2cd3@marca.python.live.cheggnet.com:9000/1'; + + return new MockRavenClient($dsn); + } + + public function testDebug() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $record = $this->getRecord(Logger::DEBUG, 'A test debug message'); + $handler->handle($record); + + $this->assertEquals($ravenClient::DEBUG, $ravenClient->lastData['level']); + $this->assertContains($record['message'], $ravenClient->lastData['message']); + } + + public function testWarning() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $record = $this->getRecord(Logger::WARNING, 'A test warning message'); + $handler->handle($record); + + $this->assertEquals($ravenClient::WARNING, $ravenClient->lastData['level']); + $this->assertContains($record['message'], $ravenClient->lastData['message']); + } + + public function testTag() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $tags = array(1, 2, 'foo'); + $record = $this->getRecord(Logger::INFO, 'test', array('tags' => $tags)); + $handler->handle($record); + + $this->assertEquals($tags, $ravenClient->lastData['tags']); + } + + public function testExtraParameters() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $checksum = '098f6bcd4621d373cade4e832627b4f6'; + $release = '05a671c66aefea124cc08b76ea6d30bb'; + $eventId = '31423'; + $record = $this->getRecord(Logger::INFO, 'test', array('checksum' => $checksum, 'release' => $release, 'event_id' => $eventId)); + $handler->handle($record); + + $this->assertEquals($checksum, $ravenClient->lastData['checksum']); + $this->assertEquals($release, $ravenClient->lastData['release']); + $this->assertEquals($eventId, $ravenClient->lastData['event_id']); + } + + public function testFingerprint() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $fingerprint = array('{{ default }}', 'other value'); + $record = $this->getRecord(Logger::INFO, 'test', array('fingerprint' => $fingerprint)); + $handler->handle($record); + + $this->assertEquals($fingerprint, $ravenClient->lastData['fingerprint']); + } + + public function testUserContext() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $recordWithNoContext = $this->getRecord(Logger::INFO, 'test with default user context'); + // set user context 'externally' + + $user = array( + 'id' => '123', + 'email' => 'test@test.com', + ); + + $recordWithContext = $this->getRecord(Logger::INFO, 'test', array('user' => $user)); + + $ravenClient->user_context(array('id' => 'test_user_id')); + // handle context + $handler->handle($recordWithContext); + $this->assertEquals($user, $ravenClient->lastData['user']); + + // check to see if its reset + $handler->handle($recordWithNoContext); + $this->assertInternalType('array', $ravenClient->context->user); + $this->assertSame('test_user_id', $ravenClient->context->user['id']); + + // handle with null context + $ravenClient->user_context(null); + $handler->handle($recordWithContext); + $this->assertEquals($user, $ravenClient->lastData['user']); + + // check to see if its reset + $handler->handle($recordWithNoContext); + $this->assertNull($ravenClient->context->user); + } + + public function testException() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + try { + $this->methodThatThrowsAnException(); + } catch (\Exception $e) { + $record = $this->getRecord(Logger::ERROR, $e->getMessage(), array('exception' => $e)); + $handler->handle($record); + } + + $this->assertEquals($record['message'], $ravenClient->lastData['message']); + } + + public function testHandleBatch() + { + $records = $this->getMultipleRecords(); + $records[] = $this->getRecord(Logger::WARNING, 'warning'); + $records[] = $this->getRecord(Logger::WARNING, 'warning'); + + $logFormatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $logFormatter->expects($this->once())->method('formatBatch'); + + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once())->method('format')->with($this->callback(function ($record) { + return $record['level'] == 400; + })); + + $handler = $this->getHandler($this->getRavenClient()); + $handler->setBatchFormatter($logFormatter); + $handler->setFormatter($formatter); + $handler->handleBatch($records); + } + + public function testHandleBatchDoNothingIfRecordsAreBelowLevel() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + ); + + $handler = $this->getMock('Monolog\Handler\RavenHandler', null, array($this->getRavenClient())); + $handler->expects($this->never())->method('handle'); + $handler->setLevel(Logger::ERROR); + $handler->handleBatch($records); + } + + public function testHandleBatchPicksProperMessage() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information 1'), + $this->getRecord(Logger::ERROR, 'error 1'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error 2'), + $this->getRecord(Logger::INFO, 'information 2'), + ); + + $logFormatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $logFormatter->expects($this->once())->method('formatBatch'); + + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once())->method('format')->with($this->callback(function ($record) use ($records) { + return $record['message'] == 'error 1'; + })); + + $handler = $this->getHandler($this->getRavenClient()); + $handler->setBatchFormatter($logFormatter); + $handler->setFormatter($formatter); + $handler->handleBatch($records); + } + + public function testGetSetBatchFormatter() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $handler->setBatchFormatter($formatter = new LineFormatter()); + $this->assertSame($formatter, $handler->getBatchFormatter()); + } + + public function testRelease() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + $release = 'v42.42.42'; + $handler->setRelease($release); + $record = $this->getRecord(Logger::INFO, 'test'); + $handler->handle($record); + $this->assertEquals($release, $ravenClient->lastData['release']); + + $localRelease = 'v41.41.41'; + $record = $this->getRecord(Logger::INFO, 'test', array('release' => $localRelease)); + $handler->handle($record); + $this->assertEquals($localRelease, $ravenClient->lastData['release']); + } + + private function methodThatThrowsAnException() + { + throw new \Exception('This is an exception'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php new file mode 100644 index 00000000..689d5278 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +class RedisHandlerTest extends TestCase +{ + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForInvalidRedis() + { + new RedisHandler(new \stdClass(), 'key'); + } + + public function testConstructorShouldWorkWithPredis() + { + $redis = $this->getMock('Predis\Client'); + $this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key')); + } + + public function testConstructorShouldWorkWithRedis() + { + $redis = $this->getMock('Redis'); + $this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key')); + } + + public function testPredisHandle() + { + $redis = $this->getMock('Predis\Client', array('rpush')); + + // Predis\Client uses rpush + $redis->expects($this->once()) + ->method('rpush') + ->with('key', 'test'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key'); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } + + public function testRedisHandle() + { + $redis = $this->getMock('Redis', array('rpush')); + + // Redis uses rPush + $redis->expects($this->once()) + ->method('rPush') + ->with('key', 'test'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key'); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } + + public function testRedisHandleCapped() + { + $redis = $this->getMock('Redis', array('multi', 'rpush', 'ltrim', 'exec')); + + // Redis uses multi + $redis->expects($this->once()) + ->method('multi') + ->will($this->returnSelf()); + + $redis->expects($this->once()) + ->method('rpush') + ->will($this->returnSelf()); + + $redis->expects($this->once()) + ->method('ltrim') + ->will($this->returnSelf()); + + $redis->expects($this->once()) + ->method('exec') + ->will($this->returnSelf()); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key', Logger::DEBUG, true, 10); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } + + public function testPredisHandleCapped() + { + $redis = $this->getMock('Predis\Client', array('transaction')); + + $redisTransaction = $this->getMock('Predis\Client', array('rpush', 'ltrim')); + + $redisTransaction->expects($this->once()) + ->method('rpush') + ->will($this->returnSelf()); + + $redisTransaction->expects($this->once()) + ->method('ltrim') + ->will($this->returnSelf()); + + // Redis uses multi + $redis->expects($this->once()) + ->method('transaction') + ->will($this->returnCallback(function ($cb) use ($redisTransaction) { + $cb($redisTransaction); + })); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key', Logger::DEBUG, true, 10); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php new file mode 100644 index 00000000..c5106175 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use PHPUnit_Framework_Error_Deprecated; + +/** + * @covers Monolog\Handler\RotatingFileHandler + */ +class RotatingFileHandlerTest extends TestCase +{ + /** + * This var should be private but then the anonymous function + * in the `setUp` method won't be able to set it. `$this` cant't + * be used in the anonymous function in `setUp` because PHP 5.3 + * does not support it. + */ + public $lastError; + + public function setUp() + { + $dir = __DIR__.'/Fixtures'; + chmod($dir, 0777); + if (!is_writable($dir)) { + $this->markTestSkipped($dir.' must be writable to test the RotatingFileHandler.'); + } + $this->lastError = null; + $self = $this; + // workaround with &$self used for PHP 5.3 + set_error_handler(function($code, $message) use (&$self) { + $self->lastError = array( + 'code' => $code, + 'message' => $message, + ); + }); + } + + private function assertErrorWasTriggered($code, $message) + { + if (empty($this->lastError)) { + $this->fail( + sprintf( + 'Failed asserting that error with code `%d` and message `%s` was triggered', + $code, + $message + ) + ); + } + $this->assertEquals($code, $this->lastError['code'], sprintf('Expected an error with code %d to be triggered, got `%s` instead', $code, $this->lastError['code'])); + $this->assertEquals($message, $this->lastError['message'], sprintf('Expected an error with message `%d` to be triggered, got `%s` instead', $message, $this->lastError['message'])); + } + + public function testRotationCreatesNewFile() + { + touch(__DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400).'.rot'); + + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot'); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord()); + + $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot'; + $this->assertTrue(file_exists($log)); + $this->assertEquals('test', file_get_contents($log)); + } + + /** + * @dataProvider rotationTests + */ + public function testRotation($createFile, $dateFormat, $timeCallback) + { + touch($old1 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-1)).'.rot'); + touch($old2 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-2)).'.rot'); + touch($old3 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-3)).'.rot'); + touch($old4 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-4)).'.rot'); + + $log = __DIR__.'/Fixtures/foo-'.date($dateFormat).'.rot'; + + if ($createFile) { + touch($log); + } + + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->setFilenameFormat('{filename}-{date}', $dateFormat); + $handler->handle($this->getRecord()); + + $handler->close(); + + $this->assertTrue(file_exists($log)); + $this->assertTrue(file_exists($old1)); + $this->assertEquals($createFile, file_exists($old2)); + $this->assertEquals($createFile, file_exists($old3)); + $this->assertEquals($createFile, file_exists($old4)); + $this->assertEquals('test', file_get_contents($log)); + } + + public function rotationTests() + { + $now = time(); + $dayCallback = function($ago) use ($now) { + return $now + 86400 * $ago; + }; + $monthCallback = function($ago) { + return gmmktime(0, 0, 0, date('n') + $ago, date('d'), date('Y')); + }; + $yearCallback = function($ago) { + return gmmktime(0, 0, 0, date('n'), date('d'), date('Y') + $ago); + }; + + return array( + 'Rotation is triggered when the file of the current day is not present' + => array(true, RotatingFileHandler::FILE_PER_DAY, $dayCallback), + 'Rotation is not triggered when the file of the current day is already present' + => array(false, RotatingFileHandler::FILE_PER_DAY, $dayCallback), + + 'Rotation is triggered when the file of the current month is not present' + => array(true, RotatingFileHandler::FILE_PER_MONTH, $monthCallback), + 'Rotation is not triggered when the file of the current month is already present' + => array(false, RotatingFileHandler::FILE_PER_MONTH, $monthCallback), + + 'Rotation is triggered when the file of the current year is not present' + => array(true, RotatingFileHandler::FILE_PER_YEAR, $yearCallback), + 'Rotation is not triggered when the file of the current year is already present' + => array(false, RotatingFileHandler::FILE_PER_YEAR, $yearCallback), + ); + } + + /** + * @dataProvider dateFormatProvider + */ + public function testAllowOnlyFixedDefinedDateFormats($dateFormat, $valid) + { + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2); + $handler->setFilenameFormat('{filename}-{date}', $dateFormat); + if (!$valid) { + $this->assertErrorWasTriggered( + E_USER_DEPRECATED, + 'Invalid date format - format must be one of RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), '. + 'RotatingFileHandler::FILE_PER_MONTH ("Y-m") or RotatingFileHandler::FILE_PER_YEAR ("Y"), '. + 'or you can set one of the date formats using slashes, underscores and/or dots instead of dashes.' + ); + } + } + + public function dateFormatProvider() + { + return array( + array(RotatingFileHandler::FILE_PER_DAY, true), + array(RotatingFileHandler::FILE_PER_MONTH, true), + array(RotatingFileHandler::FILE_PER_YEAR, true), + array('m-d-Y', false), + array('Y-m-d-h-i', false) + ); + } + + /** + * @dataProvider filenameFormatProvider + */ + public function testDisallowFilenameFormatsWithoutDate($filenameFormat, $valid) + { + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2); + $handler->setFilenameFormat($filenameFormat, RotatingFileHandler::FILE_PER_DAY); + if (!$valid) { + $this->assertErrorWasTriggered( + E_USER_DEPRECATED, + 'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.' + ); + } + } + + public function filenameFormatProvider() + { + return array( + array('{filename}', false), + array('{filename}-{date}', true), + array('{date}', true), + array('foobar-{date}', true), + array('foo-{date}-bar', true), + array('{date}-foobar', true), + array('foobar', false), + ); + } + + public function testReuseCurrentFile() + { + $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot'; + file_put_contents($log, "foo"); + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot'); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord()); + $this->assertEquals('footest', file_get_contents($log)); + } + + public function tearDown() + { + foreach (glob(__DIR__.'/Fixtures/*.rot') as $file) { + unlink($file); + } + restore_error_handler(); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.php new file mode 100644 index 00000000..b354cee1 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; + +/** + * @covers Monolog\Handler\SamplingHandler::handle + */ +class SamplingHandlerTest extends TestCase +{ + public function testHandle() + { + $testHandler = new TestHandler(); + $handler = new SamplingHandler($testHandler, 2); + for ($i = 0; $i < 10000; $i++) { + $handler->handle($this->getRecord()); + } + $count = count($testHandler->getRecords()); + // $count should be half of 10k, so between 4k and 6k + $this->assertLessThan(6000, $count); + $this->assertGreaterThan(4000, $count); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php new file mode 100644 index 00000000..e1aa96d7 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php @@ -0,0 +1,387 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Slack; + +use Monolog\Logger; +use Monolog\TestCase; + +/** + * @coversDefaultClass Monolog\Handler\Slack\SlackRecord + */ +class SlackRecordTest extends TestCase +{ + private $jsonPrettyPrintFlag; + + protected function setUp() + { + $this->jsonPrettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128; + } + + public function dataGetAttachmentColor() + { + return array( + array(Logger::DEBUG, SlackRecord::COLOR_DEFAULT), + array(Logger::INFO, SlackRecord::COLOR_GOOD), + array(Logger::NOTICE, SlackRecord::COLOR_GOOD), + array(Logger::WARNING, SlackRecord::COLOR_WARNING), + array(Logger::ERROR, SlackRecord::COLOR_DANGER), + array(Logger::CRITICAL, SlackRecord::COLOR_DANGER), + array(Logger::ALERT, SlackRecord::COLOR_DANGER), + array(Logger::EMERGENCY, SlackRecord::COLOR_DANGER), + ); + } + + /** + * @dataProvider dataGetAttachmentColor + * @param int $logLevel + * @param string $expectedColour RGB hex color or name of Slack color + * @covers ::getAttachmentColor + */ + public function testGetAttachmentColor($logLevel, $expectedColour) + { + $slackRecord = new SlackRecord(); + $this->assertSame( + $expectedColour, + $slackRecord->getAttachmentColor($logLevel) + ); + } + + public function testAddsChannel() + { + $channel = '#test'; + $record = new SlackRecord($channel); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('channel', $data); + $this->assertSame($channel, $data['channel']); + } + + public function testNoUsernameByDefault() + { + $record = new SlackRecord(); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayNotHasKey('username', $data); + } + + /** + * @return array + */ + public function dataStringify() + { + $jsonPrettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128; + + $multipleDimensions = array(array(1, 2)); + $numericKeys = array('library' => 'monolog'); + $singleDimension = array(1, 'Hello', 'Jordi'); + + return array( + array(array(), '[]'), + array($multipleDimensions, json_encode($multipleDimensions, $jsonPrettyPrintFlag)), + array($numericKeys, json_encode($numericKeys, $jsonPrettyPrintFlag)), + array($singleDimension, json_encode($singleDimension)) + ); + } + + /** + * @dataProvider dataStringify + */ + public function testStringify($fields, $expectedResult) + { + $slackRecord = new SlackRecord( + '#test', + 'test', + true, + null, + true, + true + ); + + $this->assertSame($expectedResult, $slackRecord->stringify($fields)); + } + + public function testAddsCustomUsername() + { + $username = 'Monolog bot'; + $record = new SlackRecord(null, $username); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('username', $data); + $this->assertSame($username, $data['username']); + } + + public function testNoIcon() + { + $record = new SlackRecord(); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayNotHasKey('icon_emoji', $data); + } + + public function testAddsIcon() + { + $record = $this->getRecord(); + $slackRecord = new SlackRecord(null, null, false, 'ghost'); + $data = $slackRecord->getSlackData($record); + + $slackRecord2 = new SlackRecord(null, null, false, 'http://github.com/Seldaek/monolog'); + $data2 = $slackRecord2->getSlackData($record); + + $this->assertArrayHasKey('icon_emoji', $data); + $this->assertSame(':ghost:', $data['icon_emoji']); + $this->assertArrayHasKey('icon_url', $data2); + $this->assertSame('http://github.com/Seldaek/monolog', $data2['icon_url']); + } + + public function testAttachmentsNotPresentIfNoAttachment() + { + $record = new SlackRecord(null, null, false); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayNotHasKey('attachments', $data); + } + + public function testAddsOneAttachment() + { + $record = new SlackRecord(); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('attachments', $data); + $this->assertArrayHasKey(0, $data['attachments']); + $this->assertInternalType('array', $data['attachments'][0]); + } + + public function testTextEqualsMessageIfNoAttachment() + { + $message = 'Test message'; + $record = new SlackRecord(null, null, false); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertArrayHasKey('text', $data); + $this->assertSame($message, $data['text']); + } + + public function testTextEqualsFormatterOutput() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter + ->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { return $record['message'] . 'test'; })); + + $formatter2 = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter2 + ->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { return $record['message'] . 'test1'; })); + + $message = 'Test message'; + $record = new SlackRecord(null, null, false, null, false, false, array(), $formatter); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertArrayHasKey('text', $data); + $this->assertSame($message . 'test', $data['text']); + + $record->setFormatter($formatter2); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertArrayHasKey('text', $data); + $this->assertSame($message . 'test1', $data['text']); + } + + public function testAddsFallbackAndTextToAttachment() + { + $message = 'Test message'; + $record = new SlackRecord(null); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertSame($message, $data['attachments'][0]['text']); + $this->assertSame($message, $data['attachments'][0]['fallback']); + } + + public function testMapsLevelToColorAttachmentColor() + { + $record = new SlackRecord(null); + $errorLoggerRecord = $this->getRecord(Logger::ERROR); + $emergencyLoggerRecord = $this->getRecord(Logger::EMERGENCY); + $warningLoggerRecord = $this->getRecord(Logger::WARNING); + $infoLoggerRecord = $this->getRecord(Logger::INFO); + $debugLoggerRecord = $this->getRecord(Logger::DEBUG); + + $data = $record->getSlackData($errorLoggerRecord); + $this->assertSame(SlackRecord::COLOR_DANGER, $data['attachments'][0]['color']); + + $data = $record->getSlackData($emergencyLoggerRecord); + $this->assertSame(SlackRecord::COLOR_DANGER, $data['attachments'][0]['color']); + + $data = $record->getSlackData($warningLoggerRecord); + $this->assertSame(SlackRecord::COLOR_WARNING, $data['attachments'][0]['color']); + + $data = $record->getSlackData($infoLoggerRecord); + $this->assertSame(SlackRecord::COLOR_GOOD, $data['attachments'][0]['color']); + + $data = $record->getSlackData($debugLoggerRecord); + $this->assertSame(SlackRecord::COLOR_DEFAULT, $data['attachments'][0]['color']); + } + + public function testAddsShortAttachmentWithoutContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $record = new SlackRecord(null, null, true, null, true); + $data = $record->getSlackData($this->getRecord($level, 'test', array('test' => 1))); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertSame($levelName, $attachment['title']); + $this->assertSame(array(), $attachment['fields']); + } + + public function testAddsShortAttachmentWithContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $context = array('test' => 1); + $extra = array('tags' => array('web')); + $record = new SlackRecord(null, null, true, null, true, true); + $loggerRecord = $this->getRecord($level, 'test', $context); + $loggerRecord['extra'] = $extra; + $data = $record->getSlackData($loggerRecord); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertCount(2, $attachment['fields']); + $this->assertSame($levelName, $attachment['title']); + $this->assertSame( + array( + array( + 'title' => 'Extra', + 'value' => sprintf('```%s```', json_encode($extra, $this->jsonPrettyPrintFlag)), + 'short' => false + ), + array( + 'title' => 'Context', + 'value' => sprintf('```%s```', json_encode($context, $this->jsonPrettyPrintFlag)), + 'short' => false + ) + ), + $attachment['fields'] + ); + } + + public function testAddsLongAttachmentWithoutContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $record = new SlackRecord(null, null, true, null); + $data = $record->getSlackData($this->getRecord($level, 'test', array('test' => 1))); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertCount(1, $attachment['fields']); + $this->assertSame('Message', $attachment['title']); + $this->assertSame( + array(array( + 'title' => 'Level', + 'value' => $levelName, + 'short' => false + )), + $attachment['fields'] + ); + } + + public function testAddsLongAttachmentWithContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $context = array('test' => 1); + $extra = array('tags' => array('web')); + $record = new SlackRecord(null, null, true, null, false, true); + $loggerRecord = $this->getRecord($level, 'test', $context); + $loggerRecord['extra'] = $extra; + $data = $record->getSlackData($loggerRecord); + + $expectedFields = array( + array( + 'title' => 'Level', + 'value' => $levelName, + 'short' => false, + ), + array( + 'title' => 'tags', + 'value' => sprintf('```%s```', json_encode($extra['tags'])), + 'short' => false + ), + array( + 'title' => 'test', + 'value' => $context['test'], + 'short' => false + ) + ); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertCount(3, $attachment['fields']); + $this->assertSame('Message', $attachment['title']); + $this->assertSame( + $expectedFields, + $attachment['fields'] + ); + } + + public function testAddsTimestampToAttachment() + { + $record = $this->getRecord(); + $slackRecord = new SlackRecord(); + $data = $slackRecord->getSlackData($this->getRecord()); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('ts', $attachment); + $this->assertSame($record['datetime']->getTimestamp(), $attachment['ts']); + } + + public function testExcludeExtraAndContextFields() + { + $record = $this->getRecord( + Logger::WARNING, + 'test', + array('info' => array('library' => 'monolog', 'author' => 'Jordi')) + ); + $record['extra'] = array('tags' => array('web', 'cli')); + + $slackRecord = new SlackRecord(null, null, true, null, false, true, array('context.info.library', 'extra.tags.1')); + $data = $slackRecord->getSlackData($record); + $attachment = $data['attachments'][0]; + + $expected = array( + array( + 'title' => 'info', + 'value' => sprintf('```%s```', json_encode(array('author' => 'Jordi'), $this->jsonPrettyPrintFlag)), + 'short' => false + ), + array( + 'title' => 'tags', + 'value' => sprintf('```%s```', json_encode(array('web'))), + 'short' => false + ), + ); + + foreach ($expected as $field) { + $this->assertNotFalse(array_search($field, $attachment['fields'])); + break; + } + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php new file mode 100644 index 00000000..b12b01f4 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\Slack\SlackRecord; + +/** + * @author Greg Kedzierski + * @see https://api.slack.com/ + */ +class SlackHandlerTest extends TestCase +{ + /** + * @var resource + */ + private $res; + + /** + * @var SlackHandler + */ + private $handler; + + public function setUp() + { + if (!extension_loaded('openssl')) { + $this->markTestSkipped('This test requires openssl to run'); + } + } + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/api\/chat.postMessage HTTP\/1.1\\r\\nHost: slack.com\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + } + + public function testWriteContent() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegExp('/username=Monolog/', $content); + $this->assertRegExp('/channel=channel1/', $content); + $this->assertRegExp('/token=myToken/', $content); + $this->assertRegExp('/attachments/', $content); + } + + public function testWriteContentUsesFormatterIfProvided() + { + $this->createHandler('myToken', 'channel1', 'Monolog', false); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->createHandler('myToken', 'channel1', 'Monolog', false); + $this->handler->setFormatter(new LineFormatter('foo--%message%')); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test2')); + fseek($this->res, 0); + $content2 = fread($this->res, 1024); + + $this->assertRegexp('/text=test1/', $content); + $this->assertRegexp('/text=foo--test2/', $content2); + } + + public function testWriteContentWithEmoji() + { + $this->createHandler('myToken', 'channel1', 'Monolog', true, 'alien'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/icon_emoji=%3Aalien%3A/', $content); + } + + /** + * @dataProvider provideLevelColors + */ + public function testWriteContentWithColors($level, $expectedColor) + { + $this->createHandler(); + $this->handler->handle($this->getRecord($level, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/%22color%22%3A%22'.$expectedColor.'/', $content); + } + + public function testWriteContentWithPlainTextMessage() + { + $this->createHandler('myToken', 'channel1', 'Monolog', false); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/text=test1/', $content); + } + + public function provideLevelColors() + { + return array( + array(Logger::DEBUG, urlencode(SlackRecord::COLOR_DEFAULT)), + array(Logger::INFO, SlackRecord::COLOR_GOOD), + array(Logger::NOTICE, SlackRecord::COLOR_GOOD), + array(Logger::WARNING, SlackRecord::COLOR_WARNING), + array(Logger::ERROR, SlackRecord::COLOR_DANGER), + array(Logger::CRITICAL, SlackRecord::COLOR_DANGER), + array(Logger::ALERT, SlackRecord::COLOR_DANGER), + array(Logger::EMERGENCY,SlackRecord::COLOR_DANGER), + ); + } + + private function createHandler($token = 'myToken', $channel = 'channel1', $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeExtra = false) + { + $constructorArgs = array($token, $channel, $username, $useAttachment, $iconEmoji, Logger::DEBUG, true, $useShortAttachment, $includeExtra); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\SlackHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.php new file mode 100644 index 00000000..c9229e26 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\Slack\SlackRecord; + +/** + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + * @coversDefaultClass Monolog\Handler\SlackWebhookHandler + */ +class SlackWebhookHandlerTest extends TestCase +{ + const WEBHOOK_URL = 'https://hooks.slack.com/services/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E'; + + /** + * @covers ::__construct + * @covers ::getSlackRecord + */ + public function testConstructorMinimal() + { + $handler = new SlackWebhookHandler(self::WEBHOOK_URL); + $record = $this->getRecord(); + $slackRecord = $handler->getSlackRecord(); + $this->assertInstanceOf('Monolog\Handler\Slack\SlackRecord', $slackRecord); + $this->assertEquals(array( + 'attachments' => array( + array( + 'fallback' => 'test', + 'text' => 'test', + 'color' => SlackRecord::COLOR_WARNING, + 'fields' => array( + array( + 'title' => 'Level', + 'value' => 'WARNING', + 'short' => false, + ), + ), + 'title' => 'Message', + 'mrkdwn_in' => array('fields'), + 'ts' => $record['datetime']->getTimestamp(), + ), + ), + ), $slackRecord->getSlackData($record)); + } + + /** + * @covers ::__construct + * @covers ::getSlackRecord + */ + public function testConstructorFull() + { + $handler = new SlackWebhookHandler( + self::WEBHOOK_URL, + 'test-channel', + 'test-username', + false, + ':ghost:', + false, + false, + Logger::DEBUG, + false + ); + + $slackRecord = $handler->getSlackRecord(); + $this->assertInstanceOf('Monolog\Handler\Slack\SlackRecord', $slackRecord); + $this->assertEquals(array( + 'username' => 'test-username', + 'text' => 'test', + 'channel' => 'test-channel', + 'icon_emoji' => ':ghost:', + ), $slackRecord->getSlackData($this->getRecord())); + } + + /** + * @covers ::getFormatter + */ + public function testGetFormatter() + { + $handler = new SlackWebhookHandler(self::WEBHOOK_URL); + $formatter = $handler->getFormatter(); + $this->assertInstanceOf('Monolog\Formatter\FormatterInterface', $formatter); + } + + /** + * @covers ::setFormatter + */ + public function testSetFormatter() + { + $handler = new SlackWebhookHandler(self::WEBHOOK_URL); + $formatter = new LineFormatter(); + $handler->setFormatter($formatter); + $this->assertSame($formatter, $handler->getFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php new file mode 100644 index 00000000..b1b02bde --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Haralan Dobrev + * @see https://slack.com/apps/A0F81R8ET-slackbot + * @coversDefaultClass Monolog\Handler\SlackbotHandler + */ +class SlackbotHandlerTest extends TestCase +{ + /** + * @covers ::__construct + */ + public function testConstructorMinimal() + { + $handler = new SlackbotHandler('test-team', 'test-token', 'test-channel'); + $this->assertInstanceOf('Monolog\Handler\AbstractProcessingHandler', $handler); + } + + /** + * @covers ::__construct + */ + public function testConstructorFull() + { + $handler = new SlackbotHandler( + 'test-team', + 'test-token', + 'test-channel', + Logger::DEBUG, + false + ); + $this->assertInstanceOf('Monolog\Handler\AbstractProcessingHandler', $handler); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php new file mode 100644 index 00000000..1f9c1f28 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php @@ -0,0 +1,309 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Pablo de Leon Belloc + */ +class SocketHandlerTest extends TestCase +{ + /** + * @var Monolog\Handler\SocketHandler + */ + private $handler; + + /** + * @var resource + */ + private $res; + + /** + * @expectedException UnexpectedValueException + */ + public function testInvalidHostname() + { + $this->createHandler('garbage://here'); + $this->writeRecord('data'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testBadConnectionTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setConnectionTimeout(-1); + } + + public function testSetConnectionTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setConnectionTimeout(10.1); + $this->assertEquals(10.1, $this->handler->getConnectionTimeout()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testBadTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setTimeout(-1); + } + + public function testSetTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setTimeout(10.25); + $this->assertEquals(10.25, $this->handler->getTimeout()); + } + + public function testSetWritingTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setWritingTimeout(10.25); + $this->assertEquals(10.25, $this->handler->getWritingTimeout()); + } + + public function testSetConnectionString() + { + $this->createHandler('tcp://localhost:9090'); + $this->assertEquals('tcp://localhost:9090', $this->handler->getConnectionString()); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownOnFsockopenError() + { + $this->setMockHandler(array('fsockopen')); + $this->handler->expects($this->once()) + ->method('fsockopen') + ->will($this->returnValue(false)); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownOnPfsockopenError() + { + $this->setMockHandler(array('pfsockopen')); + $this->handler->expects($this->once()) + ->method('pfsockopen') + ->will($this->returnValue(false)); + $this->handler->setPersistent(true); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownIfCannotSetTimeout() + { + $this->setMockHandler(array('streamSetTimeout')); + $this->handler->expects($this->once()) + ->method('streamSetTimeout') + ->will($this->returnValue(false)); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsOnIfFwriteReturnsFalse() + { + $this->setMockHandler(array('fwrite')); + + $callback = function ($arg) { + $map = array( + 'Hello world' => 6, + 'world' => false, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(2)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsIfStreamTimesOut() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $callback = function ($arg) { + $map = array( + 'Hello world' => 6, + 'world' => 5, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(1)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + $this->handler->expects($this->exactly(1)) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => true))); + + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsOnIncompleteWrite() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $res = $this->res; + $callback = function ($string) use ($res) { + fclose($res); + + return strlen('Hello'); + }; + + $this->handler->expects($this->exactly(1)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + $this->handler->expects($this->exactly(1)) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => false))); + + $this->writeRecord('Hello world'); + } + + public function testWriteWithMemoryFile() + { + $this->setMockHandler(); + $this->writeRecord('test1'); + $this->writeRecord('test2'); + $this->writeRecord('test3'); + fseek($this->res, 0); + $this->assertEquals('test1test2test3', fread($this->res, 1024)); + } + + public function testWriteWithMock() + { + $this->setMockHandler(array('fwrite')); + + $callback = function ($arg) { + $map = array( + 'Hello world' => 6, + 'world' => 5, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(2)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + + $this->writeRecord('Hello world'); + } + + public function testClose() + { + $this->setMockHandler(); + $this->writeRecord('Hello world'); + $this->assertInternalType('resource', $this->res); + $this->handler->close(); + $this->assertFalse(is_resource($this->res), "Expected resource to be closed after closing handler"); + } + + public function testCloseDoesNotClosePersistentSocket() + { + $this->setMockHandler(); + $this->handler->setPersistent(true); + $this->writeRecord('Hello world'); + $this->assertTrue(is_resource($this->res)); + $this->handler->close(); + $this->assertTrue(is_resource($this->res)); + } + + /** + * @expectedException \RuntimeException + */ + public function testAvoidInfiniteLoopWhenNoDataIsWrittenForAWritingTimeoutSeconds() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $this->handler->expects($this->any()) + ->method('fwrite') + ->will($this->returnValue(0)); + + $this->handler->expects($this->any()) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => false))); + + $this->handler->setWritingTimeout(1); + + $this->writeRecord('Hello world'); + } + + private function createHandler($connectionString) + { + $this->handler = new SocketHandler($connectionString); + $this->handler->setFormatter($this->getIdentityFormatter()); + } + + private function writeRecord($string) + { + $this->handler->handle($this->getRecord(Logger::WARNING, $string)); + } + + private function setMockHandler(array $methods = array()) + { + $this->res = fopen('php://memory', 'a'); + + $defaultMethods = array('fsockopen', 'pfsockopen', 'streamSetTimeout'); + $newMethods = array_diff($methods, $defaultMethods); + + $finalMethods = array_merge($defaultMethods, $newMethods); + + $this->handler = $this->getMock( + '\Monolog\Handler\SocketHandler', $finalMethods, array('localhost:1234') + ); + + if (!in_array('fsockopen', $methods)) { + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + } + + if (!in_array('pfsockopen', $methods)) { + $this->handler->expects($this->any()) + ->method('pfsockopen') + ->will($this->returnValue($this->res)); + } + + if (!in_array('streamSetTimeout', $methods)) { + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + } + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php new file mode 100644 index 00000000..487030fe --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class StreamHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWrite() + { + $handle = fopen('php://memory', 'a+'); + $handler = new StreamHandler($handle); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::WARNING, 'test')); + $handler->handle($this->getRecord(Logger::WARNING, 'test2')); + $handler->handle($this->getRecord(Logger::WARNING, 'test3')); + fseek($handle, 0); + $this->assertEquals('testtest2test3', fread($handle, 100)); + } + + /** + * @covers Monolog\Handler\StreamHandler::close + */ + public function testCloseKeepsExternalHandlersOpen() + { + $handle = fopen('php://memory', 'a+'); + $handler = new StreamHandler($handle); + $this->assertTrue(is_resource($handle)); + $handler->close(); + $this->assertTrue(is_resource($handle)); + } + + /** + * @covers Monolog\Handler\StreamHandler::close + */ + public function testClose() + { + $handler = new StreamHandler('php://memory'); + $handler->handle($this->getRecord(Logger::WARNING, 'test')); + $streamProp = new \ReflectionProperty('Monolog\Handler\StreamHandler', 'stream'); + $streamProp->setAccessible(true); + $handle = $streamProp->getValue($handler); + + $this->assertTrue(is_resource($handle)); + $handler->close(); + $this->assertFalse(is_resource($handle)); + } + + /** + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteCreatesTheStreamResource() + { + $handler = new StreamHandler('php://memory'); + $handler->handle($this->getRecord()); + } + + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteLocking() + { + $temp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'monolog_locked_log'; + $handler = new StreamHandler($temp, Logger::DEBUG, true, null, true); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException LogicException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteMissingResource() + { + $handler = new StreamHandler(null); + $handler->handle($this->getRecord()); + } + + public function invalidArgumentProvider() + { + return array( + array(1), + array(array()), + array(array('bogus://url')), + ); + } + + /** + * @dataProvider invalidArgumentProvider + * @expectedException InvalidArgumentException + * @covers Monolog\Handler\StreamHandler::__construct + */ + public function testWriteInvalidArgument($invalidArgument) + { + $handler = new StreamHandler($invalidArgument); + } + + /** + * @expectedException UnexpectedValueException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteInvalidResource() + { + $handler = new StreamHandler('bogus://url'); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException UnexpectedValueException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingResource() + { + $handler = new StreamHandler('ftp://foo/bar/baz/'.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingPath() + { + $handler = new StreamHandler(sys_get_temp_dir().'/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingFileResource() + { + $handler = new StreamHandler('file://'.sys_get_temp_dir().'/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException Exception + * @expectedExceptionMessageRegExp /There is no existing directory at/ + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingAndNotCreatablePath() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Permissions checks can not run on windows'); + } + $handler = new StreamHandler('/foo/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException Exception + * @expectedExceptionMessageRegExp /There is no existing directory at/ + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingAndNotCreatableFileResource() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Permissions checks can not run on windows'); + } + $handler = new StreamHandler('file:///foo/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php new file mode 100644 index 00000000..85886910 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\TestCase; + +class SwiftMailerHandlerTest extends TestCase +{ + /** @var \Swift_Mailer|\PHPUnit_Framework_MockObject_MockObject */ + private $mailer; + + public function setUp() + { + $this->mailer = $this + ->getMockBuilder('Swift_Mailer') + ->disableOriginalConstructor() + ->getMock(); + } + + public function testMessageCreationIsLazyWhenUsingCallback() + { + $this->mailer->expects($this->never()) + ->method('send'); + + $callback = function () { + throw new \RuntimeException('Swift_Message creation callback should not have been called in this test'); + }; + $handler = new SwiftMailerHandler($this->mailer, $callback); + + $records = array( + $this->getRecord(Logger::DEBUG), + $this->getRecord(Logger::INFO), + ); + $handler->handleBatch($records); + } + + public function testMessageCanBeCustomizedGivenLoggedData() + { + // Wire Mailer to expect a specific Swift_Message with a customized Subject + $expectedMessage = new \Swift_Message(); + $this->mailer->expects($this->once()) + ->method('send') + ->with($this->callback(function ($value) use ($expectedMessage) { + return $value instanceof \Swift_Message + && $value->getSubject() === 'Emergency' + && $value === $expectedMessage; + })); + + // Callback dynamically changes subject based on number of logged records + $callback = function ($content, array $records) use ($expectedMessage) { + $subject = count($records) > 0 ? 'Emergency' : 'Normal'; + $expectedMessage->setSubject($subject); + + return $expectedMessage; + }; + $handler = new SwiftMailerHandler($this->mailer, $callback); + + // Logging 1 record makes this an Emergency + $records = array( + $this->getRecord(Logger::EMERGENCY), + ); + $handler->handleBatch($records); + } + + public function testMessageSubjectFormatting() + { + // Wire Mailer to expect a specific Swift_Message with a customized Subject + $messageTemplate = new \Swift_Message(); + $messageTemplate->setSubject('Alert: %level_name% %message%'); + $receivedMessage = null; + + $this->mailer->expects($this->once()) + ->method('send') + ->with($this->callback(function ($value) use (&$receivedMessage) { + $receivedMessage = $value; + return true; + })); + + $handler = new SwiftMailerHandler($this->mailer, $messageTemplate); + + $records = array( + $this->getRecord(Logger::EMERGENCY), + ); + $handler->handleBatch($records); + + $this->assertEquals('Alert: EMERGENCY test', $receivedMessage->getSubject()); + } + + public function testMessageHaveUniqueId() + { + $messageTemplate = \Swift_Message::newInstance(); + $handler = new SwiftMailerHandler($this->mailer, $messageTemplate); + + $method = new \ReflectionMethod('Monolog\Handler\SwiftMailerHandler', 'buildMessage'); + $method->setAccessible(true); + $method->invokeArgs($handler, array($messageTemplate, array())); + + $builtMessage1 = $method->invoke($handler, $messageTemplate, array()); + $builtMessage2 = $method->invoke($handler, $messageTemplate, array()); + + $this->assertFalse($builtMessage1->getId() === $builtMessage2->getId(), 'Two different messages have the same id'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php new file mode 100644 index 00000000..8f9e46bf --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.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 Monolog\Handler; + +use Monolog\Logger; + +class SyslogHandlerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Handler\SyslogHandler::__construct + */ + public function testConstruct() + { + $handler = new SyslogHandler('test'); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', LOG_USER); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', 'user'); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', LOG_USER, Logger::DEBUG, true, LOG_PERROR); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + } + + /** + * @covers Monolog\Handler\SyslogHandler::__construct + */ + public function testConstructInvalidFacility() + { + $this->setExpectedException('UnexpectedValueException'); + $handler = new SyslogHandler('test', 'unknown'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php new file mode 100644 index 00000000..aa4a3341 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; + +/** + * @requires extension sockets + */ +class SyslogUdpHandlerTest extends TestCase +{ + /** + * @expectedException UnexpectedValueException + */ + public function testWeValidateFacilities() + { + $handler = new SyslogUdpHandler("ip", null, "invalidFacility"); + } + + public function testWeSplitIntoLines() + { + $handler = new SyslogUdpHandler("127.0.0.1", 514, "authpriv"); + $handler->setFormatter(new \Monolog\Formatter\ChromePHPFormatter()); + + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('write'), array('lol', 'lol')); + $socket->expects($this->at(0)) + ->method('write') + ->with("lol", "<".(LOG_AUTHPRIV + LOG_WARNING).">1 "); + $socket->expects($this->at(1)) + ->method('write') + ->with("hej", "<".(LOG_AUTHPRIV + LOG_WARNING).">1 "); + + $handler->setSocket($socket); + + $handler->handle($this->getRecordWithMessage("hej\nlol")); + } + + public function testSplitWorksOnEmptyMsg() + { + $handler = new SyslogUdpHandler("127.0.0.1", 514, "authpriv"); + $handler->setFormatter($this->getIdentityFormatter()); + + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('write'), array('lol', 'lol')); + $socket->expects($this->never()) + ->method('write'); + + $handler->setSocket($socket); + + $handler->handle($this->getRecordWithMessage(null)); + } + + protected function getRecordWithMessage($msg) + { + return array('message' => $msg, 'level' => \Monolog\Logger::WARNING, 'context' => null, 'extra' => array(), 'channel' => 'lol'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php new file mode 100644 index 00000000..bfb8d3df --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\TestHandler + */ +class TestHandlerTest extends TestCase +{ + /** + * @dataProvider methodProvider + */ + public function testHandler($method, $level) + { + $handler = new TestHandler; + $record = $this->getRecord($level, 'test'.$method); + $this->assertFalse($handler->hasRecords($level)); + $this->assertFalse($handler->hasRecord($record, $level)); + $this->assertFalse($handler->{'has'.$method}($record), 'has'.$method); + $this->assertFalse($handler->{'has'.$method.'ThatContains'}('test'), 'has'.$method.'ThatContains'); + $this->assertFalse($handler->{'has'.$method.'ThatPasses'}(function ($rec) { + return true; + }), 'has'.$method.'ThatPasses'); + $this->assertFalse($handler->{'has'.$method.'ThatMatches'}('/test\w+/')); + $this->assertFalse($handler->{'has'.$method.'Records'}(), 'has'.$method.'Records'); + $handler->handle($record); + + $this->assertFalse($handler->{'has'.$method}('bar'), 'has'.$method); + $this->assertTrue($handler->hasRecords($level)); + $this->assertTrue($handler->hasRecord($record, $level)); + $this->assertTrue($handler->{'has'.$method}($record), 'has'.$method); + $this->assertTrue($handler->{'has'.$method}('test'.$method), 'has'.$method); + $this->assertTrue($handler->{'has'.$method.'ThatContains'}('test'), 'has'.$method.'ThatContains'); + $this->assertTrue($handler->{'has'.$method.'ThatPasses'}(function ($rec) { + return true; + }), 'has'.$method.'ThatPasses'); + $this->assertTrue($handler->{'has'.$method.'ThatMatches'}('/test\w+/')); + $this->assertTrue($handler->{'has'.$method.'Records'}(), 'has'.$method.'Records'); + + $records = $handler->getRecords(); + unset($records[0]['formatted']); + $this->assertEquals(array($record), $records); + } + + public function methodProvider() + { + return array( + array('Emergency', Logger::EMERGENCY), + array('Alert' , Logger::ALERT), + array('Critical' , Logger::CRITICAL), + array('Error' , Logger::ERROR), + array('Warning' , Logger::WARNING), + array('Info' , Logger::INFO), + array('Notice' , Logger::NOTICE), + array('Debug' , Logger::DEBUG), + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php new file mode 100644 index 00000000..fa524d00 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Handler\SyslogUdp\UdpSocket; + +/** + * @requires extension sockets + */ +class UdpSocketTest extends TestCase +{ + public function testWeDoNotTruncateShortMessages() + { + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('send'), array('lol', 'lol')); + + $socket->expects($this->at(0)) + ->method('send') + ->with("HEADER: The quick brown fox jumps over the lazy dog"); + + $socket->write("The quick brown fox jumps over the lazy dog", "HEADER: "); + } + + public function testLongMessagesAreTruncated() + { + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('send'), array('lol', 'lol')); + + $truncatedString = str_repeat("derp", 16254).'d'; + + $socket->expects($this->exactly(1)) + ->method('send') + ->with("HEADER" . $truncatedString); + + $longString = str_repeat("derp", 20000); + + $socket->write($longString, "HEADER"); + } + + public function testDoubleCloseDoesNotError() + { + $socket = new UdpSocket('127.0.0.1', 514); + $socket->close(); + $socket->close(); + } + + /** + * @expectedException LogicException + */ + public function testWriteAfterCloseErrors() + { + $socket = new UdpSocket('127.0.0.1', 514); + $socket->close(); + $socket->write('foo', "HEADER"); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php new file mode 100644 index 00000000..8d37a1fc --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class WhatFailureGroupHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorOnlyTakesHandler() + { + new WhatFailureGroupHandler(array(new TestHandler(), "foo")); + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::__construct + * @covers Monolog\Handler\WhatFailureGroupHandler::handle + */ + public function testHandle() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new WhatFailureGroupHandler($testHandlers); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::handleBatch + */ + public function testHandleBatch() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new WhatFailureGroupHandler($testHandlers); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::isHandling + */ + public function testIsHandling() + { + $testHandlers = array(new TestHandler(Logger::ERROR), new TestHandler(Logger::WARNING)); + $handler = new WhatFailureGroupHandler($testHandlers); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::ERROR))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::WARNING))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new WhatFailureGroupHandler(array($test)); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::handle + */ + public function testHandleException() + { + $test = new TestHandler(); + $exception = new ExceptionTestHandler(); + $handler = new WhatFailureGroupHandler(array($exception, $test, $exception)); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } +} + +class ExceptionTestHandler extends TestHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + parent::handle($record); + + throw new \Exception("ExceptionTestHandler::handle"); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php new file mode 100644 index 00000000..69b001ea --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.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 Monolog\Handler; + +use Monolog\TestCase; + +class ZendMonitorHandlerTest extends TestCase +{ + protected $zendMonitorHandler; + + public function setUp() + { + if (!function_exists('zend_monitor_custom_event')) { + $this->markTestSkipped('ZendServer is not installed'); + } + } + + /** + * @covers Monolog\Handler\ZendMonitorHandler::write + */ + public function testWrite() + { + $record = $this->getRecord(); + $formatterResult = array( + 'message' => $record['message'], + ); + + $zendMonitor = $this->getMockBuilder('Monolog\Handler\ZendMonitorHandler') + ->setMethods(array('writeZendMonitorCustomEvent', 'getDefaultFormatter')) + ->getMock(); + + $formatterMock = $this->getMockBuilder('Monolog\Formatter\NormalizerFormatter') + ->disableOriginalConstructor() + ->getMock(); + + $formatterMock->expects($this->once()) + ->method('format') + ->will($this->returnValue($formatterResult)); + + $zendMonitor->expects($this->once()) + ->method('getDefaultFormatter') + ->will($this->returnValue($formatterMock)); + + $levelMap = $zendMonitor->getLevelMap(); + + $zendMonitor->expects($this->once()) + ->method('writeZendMonitorCustomEvent') + ->with($levelMap[$record['level']], $record['message'], $formatterResult); + + $zendMonitor->handle($record); + } + + /** + * @covers Monolog\Handler\ZendMonitorHandler::getDefaultFormatter + */ + public function testGetDefaultFormatterReturnsNormalizerFormatter() + { + $zendMonitor = new ZendMonitorHandler(); + $this->assertInstanceOf('Monolog\Formatter\NormalizerFormatter', $zendMonitor->getDefaultFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/LoggerTest.php b/vendor/monolog/monolog/tests/Monolog/LoggerTest.php new file mode 100644 index 00000000..1ecc34a0 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/LoggerTest.php @@ -0,0 +1,548 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Processor\WebProcessor; +use Monolog\Handler\TestHandler; + +class LoggerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Logger::getName + */ + public function testGetName() + { + $logger = new Logger('foo'); + $this->assertEquals('foo', $logger->getName()); + } + + /** + * @covers Monolog\Logger::getLevelName + */ + public function testGetLevelName() + { + $this->assertEquals('ERROR', Logger::getLevelName(Logger::ERROR)); + } + + /** + * @covers Monolog\Logger::withName + */ + public function testWithName() + { + $first = new Logger('first', array($handler = new TestHandler())); + $second = $first->withName('second'); + + $this->assertSame('first', $first->getName()); + $this->assertSame('second', $second->getName()); + $this->assertSame($handler, $second->popHandler()); + } + + /** + * @covers Monolog\Logger::toMonologLevel + */ + public function testConvertPSR3ToMonologLevel() + { + $this->assertEquals(Logger::toMonologLevel('debug'), 100); + $this->assertEquals(Logger::toMonologLevel('info'), 200); + $this->assertEquals(Logger::toMonologLevel('notice'), 250); + $this->assertEquals(Logger::toMonologLevel('warning'), 300); + $this->assertEquals(Logger::toMonologLevel('error'), 400); + $this->assertEquals(Logger::toMonologLevel('critical'), 500); + $this->assertEquals(Logger::toMonologLevel('alert'), 550); + $this->assertEquals(Logger::toMonologLevel('emergency'), 600); + } + + /** + * @covers Monolog\Logger::getLevelName + * @expectedException InvalidArgumentException + */ + public function testGetLevelNameThrows() + { + Logger::getLevelName(5); + } + + /** + * @covers Monolog\Logger::__construct + */ + public function testChannel() + { + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->addWarning('test'); + list($record) = $handler->getRecords(); + $this->assertEquals('foo', $record['channel']); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testLog() + { + $logger = new Logger(__METHOD__); + + $handler = $this->getMock('Monolog\Handler\NullHandler', array('handle')); + $handler->expects($this->once()) + ->method('handle'); + $logger->pushHandler($handler); + + $this->assertTrue($logger->addWarning('test')); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testLogNotHandled() + { + $logger = new Logger(__METHOD__); + + $handler = $this->getMock('Monolog\Handler\NullHandler', array('handle'), array(Logger::ERROR)); + $handler->expects($this->never()) + ->method('handle'); + $logger->pushHandler($handler); + + $this->assertFalse($logger->addWarning('test')); + } + + public function testHandlersInCtor() + { + $handler1 = new TestHandler; + $handler2 = new TestHandler; + $logger = new Logger(__METHOD__, array($handler1, $handler2)); + + $this->assertEquals($handler1, $logger->popHandler()); + $this->assertEquals($handler2, $logger->popHandler()); + } + + public function testProcessorsInCtor() + { + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + $logger = new Logger(__METHOD__, array(), array($processor1, $processor2)); + + $this->assertEquals($processor1, $logger->popProcessor()); + $this->assertEquals($processor2, $logger->popProcessor()); + } + + /** + * @covers Monolog\Logger::pushHandler + * @covers Monolog\Logger::popHandler + * @expectedException LogicException + */ + public function testPushPopHandler() + { + $logger = new Logger(__METHOD__); + $handler1 = new TestHandler; + $handler2 = new TestHandler; + + $logger->pushHandler($handler1); + $logger->pushHandler($handler2); + + $this->assertEquals($handler2, $logger->popHandler()); + $this->assertEquals($handler1, $logger->popHandler()); + $logger->popHandler(); + } + + /** + * @covers Monolog\Logger::setHandlers + */ + public function testSetHandlers() + { + $logger = new Logger(__METHOD__); + $handler1 = new TestHandler; + $handler2 = new TestHandler; + + $logger->pushHandler($handler1); + $logger->setHandlers(array($handler2)); + + // handler1 has been removed + $this->assertEquals(array($handler2), $logger->getHandlers()); + + $logger->setHandlers(array( + "AMapKey" => $handler1, + "Woop" => $handler2, + )); + + // Keys have been scrubbed + $this->assertEquals(array($handler1, $handler2), $logger->getHandlers()); + } + + /** + * @covers Monolog\Logger::pushProcessor + * @covers Monolog\Logger::popProcessor + * @expectedException LogicException + */ + public function testPushPopProcessor() + { + $logger = new Logger(__METHOD__); + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + + $logger->pushProcessor($processor1); + $logger->pushProcessor($processor2); + + $this->assertEquals($processor2, $logger->popProcessor()); + $this->assertEquals($processor1, $logger->popProcessor()); + $logger->popProcessor(); + } + + /** + * @covers Monolog\Logger::pushProcessor + * @expectedException InvalidArgumentException + */ + public function testPushProcessorWithNonCallable() + { + $logger = new Logger(__METHOD__); + + $logger->pushProcessor(new \stdClass()); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsAreExecuted() + { + $logger = new Logger(__METHOD__); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->pushProcessor(function ($record) { + $record['extra']['win'] = true; + + return $record; + }); + $logger->addError('test'); + list($record) = $handler->getRecords(); + $this->assertTrue($record['extra']['win']); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsAreCalledOnlyOnce() + { + $logger = new Logger(__METHOD__); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler->expects($this->any()) + ->method('handle') + ->will($this->returnValue(true)) + ; + $logger->pushHandler($handler); + + $processor = $this->getMockBuilder('Monolog\Processor\WebProcessor') + ->disableOriginalConstructor() + ->setMethods(array('__invoke')) + ->getMock() + ; + $processor->expects($this->once()) + ->method('__invoke') + ->will($this->returnArgument(0)) + ; + $logger->pushProcessor($processor); + + $logger->addError('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsNotCalledWhenNotHandled() + { + $logger = new Logger(__METHOD__); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler); + $that = $this; + $logger->pushProcessor(function ($record) use ($that) { + $that->fail('The processor should not be called'); + }); + $logger->addAlert('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testHandlersNotCalledBeforeFirstHandling() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->never()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler2); + + $handler3 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler3->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler3->expects($this->never()) + ->method('handle') + ; + $logger->pushHandler($handler3); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testHandlersNotCalledBeforeFirstHandlingWithAssocArray() + { + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->never()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + + $handler3 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler3->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler3->expects($this->never()) + ->method('handle') + ; + + $logger = new Logger(__METHOD__, array('last' => $handler3, 'second' => $handler2, 'first' => $handler1)); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testBubblingWhenTheHandlerReturnsFalse() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler2); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testNotBubblingWhenTheHandlerReturnsTrue() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler1->expects($this->never()) + ->method('handle') + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(true)) + ; + $logger->pushHandler($handler2); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::isHandling + */ + public function testIsHandling() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + + $logger->pushHandler($handler1); + $this->assertFalse($logger->isHandling(Logger::DEBUG)); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + + $logger->pushHandler($handler2); + $this->assertTrue($logger->isHandling(Logger::DEBUG)); + } + + /** + * @dataProvider logMethodProvider + * @covers Monolog\Logger::addDebug + * @covers Monolog\Logger::addInfo + * @covers Monolog\Logger::addNotice + * @covers Monolog\Logger::addWarning + * @covers Monolog\Logger::addError + * @covers Monolog\Logger::addCritical + * @covers Monolog\Logger::addAlert + * @covers Monolog\Logger::addEmergency + * @covers Monolog\Logger::debug + * @covers Monolog\Logger::info + * @covers Monolog\Logger::notice + * @covers Monolog\Logger::warn + * @covers Monolog\Logger::err + * @covers Monolog\Logger::crit + * @covers Monolog\Logger::alert + * @covers Monolog\Logger::emerg + */ + public function testLogMethods($method, $expectedLevel) + { + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->{$method}('test'); + list($record) = $handler->getRecords(); + $this->assertEquals($expectedLevel, $record['level']); + } + + public function logMethodProvider() + { + return array( + // monolog methods + array('addDebug', Logger::DEBUG), + array('addInfo', Logger::INFO), + array('addNotice', Logger::NOTICE), + array('addWarning', Logger::WARNING), + array('addError', Logger::ERROR), + array('addCritical', Logger::CRITICAL), + array('addAlert', Logger::ALERT), + array('addEmergency', Logger::EMERGENCY), + + // ZF/Sf2 compat methods + array('debug', Logger::DEBUG), + array('info', Logger::INFO), + array('notice', Logger::NOTICE), + array('warn', Logger::WARNING), + array('err', Logger::ERROR), + array('crit', Logger::CRITICAL), + array('alert', Logger::ALERT), + array('emerg', Logger::EMERGENCY), + ); + } + + /** + * @dataProvider setTimezoneProvider + * @covers Monolog\Logger::setTimezone + */ + public function testSetTimezone($tz) + { + Logger::setTimezone($tz); + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->info('test'); + list($record) = $handler->getRecords(); + $this->assertEquals($tz, $record['datetime']->getTimezone()); + } + + public function setTimezoneProvider() + { + return array_map( + function ($tz) { return array(new \DateTimeZone($tz)); }, + \DateTimeZone::listIdentifiers() + ); + } + + /** + * @dataProvider useMicrosecondTimestampsProvider + * @covers Monolog\Logger::useMicrosecondTimestamps + * @covers Monolog\Logger::addRecord + */ + public function testUseMicrosecondTimestamps($micro, $assert) + { + $logger = new Logger('foo'); + $logger->useMicrosecondTimestamps($micro); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->info('test'); + list($record) = $handler->getRecords(); + $this->{$assert}('000000', $record['datetime']->format('u')); + } + + public function useMicrosecondTimestampsProvider() + { + return array( + // this has a very small chance of a false negative (1/10^6) + 'with microseconds' => array(true, 'assertNotSame'), + 'without microseconds' => array(false, PHP_VERSION_ID >= 70100 ? 'assertNotSame' : 'assertSame'), + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.php new file mode 100644 index 00000000..5adb505d --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.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 Monolog\Processor; + +use Monolog\TestCase; + +class GitProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\GitProcessor::__invoke + */ + public function testProcessor() + { + $processor = new GitProcessor(); + $record = $processor($this->getRecord()); + + $this->assertArrayHasKey('git', $record['extra']); + $this->assertTrue(!is_array($record['extra']['git']['branch'])); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php new file mode 100644 index 00000000..0dd411d7 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Acme; + +class Tester +{ + public function test($handler, $record) + { + $handler->handle($record); + } +} + +function tester($handler, $record) +{ + $handler->handle($record); +} + +namespace Monolog\Processor; + +use Monolog\Logger; +use Monolog\TestCase; +use Monolog\Handler\TestHandler; + +class IntrospectionProcessorTest extends TestCase +{ + public function getHandler() + { + $processor = new IntrospectionProcessor(); + $handler = new TestHandler(); + $handler->pushProcessor($processor); + + return $handler; + } + + public function testProcessorFromClass() + { + $handler = $this->getHandler(); + $tester = new \Acme\Tester; + $tester->test($handler, $this->getRecord()); + list($record) = $handler->getRecords(); + $this->assertEquals(__FILE__, $record['extra']['file']); + $this->assertEquals(18, $record['extra']['line']); + $this->assertEquals('Acme\Tester', $record['extra']['class']); + $this->assertEquals('test', $record['extra']['function']); + } + + public function testProcessorFromFunc() + { + $handler = $this->getHandler(); + \Acme\tester($handler, $this->getRecord()); + list($record) = $handler->getRecords(); + $this->assertEquals(__FILE__, $record['extra']['file']); + $this->assertEquals(24, $record['extra']['line']); + $this->assertEquals(null, $record['extra']['class']); + $this->assertEquals('Acme\tester', $record['extra']['function']); + } + + public function testLevelTooLow() + { + $input = array( + 'level' => Logger::DEBUG, + 'extra' => array(), + ); + + $expected = $input; + + $processor = new IntrospectionProcessor(Logger::CRITICAL); + $actual = $processor($input); + + $this->assertEquals($expected, $actual); + } + + public function testLevelEqual() + { + $input = array( + 'level' => Logger::CRITICAL, + 'extra' => array(), + ); + + $expected = $input; + $expected['extra'] = array( + 'file' => null, + 'line' => null, + 'class' => 'ReflectionMethod', + 'function' => 'invokeArgs', + ); + + $processor = new IntrospectionProcessor(Logger::CRITICAL); + $actual = $processor($input); + + $this->assertEquals($expected, $actual); + } + + public function testLevelHigher() + { + $input = array( + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ); + + $expected = $input; + $expected['extra'] = array( + 'file' => null, + 'line' => null, + 'class' => 'ReflectionMethod', + 'function' => 'invokeArgs', + ); + + $processor = new IntrospectionProcessor(Logger::CRITICAL); + $actual = $processor($input); + + $this->assertEquals($expected, $actual); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php new file mode 100644 index 00000000..eb666144 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.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 Monolog\Processor; + +use Monolog\TestCase; + +class MemoryPeakUsageProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MemoryPeakUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessor() + { + $processor = new MemoryPeakUsageProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_peak_usage', $record['extra']); + $this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_peak_usage']); + } + + /** + * @covers Monolog\Processor\MemoryPeakUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessorWithoutFormatting() + { + $processor = new MemoryPeakUsageProcessor(true, false); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_peak_usage', $record['extra']); + $this->assertInternalType('int', $record['extra']['memory_peak_usage']); + $this->assertGreaterThan(0, $record['extra']['memory_peak_usage']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php new file mode 100644 index 00000000..4692dbfc --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.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 Monolog\Processor; + +use Monolog\TestCase; + +class MemoryUsageProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MemoryUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessor() + { + $processor = new MemoryUsageProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_usage', $record['extra']); + $this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_usage']); + } + + /** + * @covers Monolog\Processor\MemoryUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessorWithoutFormatting() + { + $processor = new MemoryUsageProcessor(true, false); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_usage', $record['extra']); + $this->assertInternalType('int', $record['extra']['memory_usage']); + $this->assertGreaterThan(0, $record['extra']['memory_usage']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.php new file mode 100644 index 00000000..11f2b35a --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.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 Monolog\Processor; + +use Monolog\TestCase; + +class MercurialProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MercurialProcessor::__invoke + */ + public function testProcessor() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + exec("where hg 2>NUL", $output, $result); + } else { + exec("which hg 2>/dev/null >/dev/null", $output, $result); + } + if ($result != 0) { + $this->markTestSkipped('hg is missing'); + return; + } + + `hg init`; + $processor = new MercurialProcessor(); + $record = $processor($this->getRecord()); + + $this->assertArrayHasKey('hg', $record['extra']); + $this->assertTrue(!is_array($record['extra']['hg']['branch'])); + $this->assertTrue(!is_array($record['extra']['hg']['revision'])); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php new file mode 100644 index 00000000..458d2a33 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.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 Monolog\Processor; + +use Monolog\TestCase; + +class ProcessIdProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\ProcessIdProcessor::__invoke + */ + public function testProcessor() + { + $processor = new ProcessIdProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('process_id', $record['extra']); + $this->assertInternalType('int', $record['extra']['process_id']); + $this->assertGreaterThan(0, $record['extra']['process_id']); + $this->assertEquals(getmypid(), $record['extra']['process_id']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php new file mode 100644 index 00000000..029a0c02 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.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 Monolog\Processor; + +class PsrLogMessageProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getPairs + */ + public function testReplacement($val, $expected) + { + $proc = new PsrLogMessageProcessor; + + $message = $proc(array( + 'message' => '{foo}', + 'context' => array('foo' => $val), + )); + $this->assertEquals($expected, $message['message']); + } + + public function getPairs() + { + return array( + array('foo', 'foo'), + array('3', '3'), + array(3, '3'), + array(null, ''), + array(true, '1'), + array(false, ''), + array(new \stdClass, '[object stdClass]'), + array(array(), '[array]'), + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.php new file mode 100644 index 00000000..0d860c61 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.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 Monolog\Processor; + +use Monolog\TestCase; + +class TagProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\TagProcessor::__invoke + */ + public function testProcessor() + { + $tags = array(1, 2, 3); + $processor = new TagProcessor($tags); + $record = $processor($this->getRecord()); + + $this->assertEquals($tags, $record['extra']['tags']); + } + + /** + * @covers Monolog\Processor\TagProcessor::__invoke + */ + public function testProcessorTagModification() + { + $tags = array(1, 2, 3); + $processor = new TagProcessor($tags); + + $record = $processor($this->getRecord()); + $this->assertEquals($tags, $record['extra']['tags']); + + $processor->setTags(array('a', 'b')); + $record = $processor($this->getRecord()); + $this->assertEquals(array('a', 'b'), $record['extra']['tags']); + + $processor->addTags(array('a', 'c', 'foo' => 'bar')); + $record = $processor($this->getRecord()); + $this->assertEquals(array('a', 'b', 'a', 'c', 'foo' => 'bar'), $record['extra']['tags']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php new file mode 100644 index 00000000..5d13058f --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.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 Monolog\Processor; + +use Monolog\TestCase; + +class UidProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\UidProcessor::__invoke + */ + public function testProcessor() + { + $processor = new UidProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('uid', $record['extra']); + } + + public function testGetUid() + { + $processor = new UidProcessor(10); + $this->assertEquals(10, strlen($processor->getUid())); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php new file mode 100644 index 00000000..4105baf7 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class WebProcessorTest extends TestCase +{ + public function testProcessor() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'HTTP_REFERER' => 'D', + 'SERVER_NAME' => 'F', + 'UNIQUE_ID' => 'G', + ); + + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertEquals($server['REQUEST_URI'], $record['extra']['url']); + $this->assertEquals($server['REMOTE_ADDR'], $record['extra']['ip']); + $this->assertEquals($server['REQUEST_METHOD'], $record['extra']['http_method']); + $this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']); + $this->assertEquals($server['SERVER_NAME'], $record['extra']['server']); + $this->assertEquals($server['UNIQUE_ID'], $record['extra']['unique_id']); + } + + public function testProcessorDoNothingIfNoRequestUri() + { + $server = array( + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertEmpty($record['extra']); + } + + public function testProcessorReturnNullIfNoHttpReferer() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertNull($record['extra']['referrer']); + } + + public function testProcessorDoesNotAddUniqueIdIfNotPresent() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertFalse(isset($record['extra']['unique_id'])); + } + + public function testProcessorAddsOnlyRequestedExtraFields() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + + $processor = new WebProcessor($server, array('url', 'http_method')); + $record = $processor($this->getRecord()); + + $this->assertSame(array('url' => 'A', 'http_method' => 'C'), $record['extra']); + } + + public function testProcessorConfiguringOfExtraFields() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + + $processor = new WebProcessor($server, array('url' => 'REMOTE_ADDR')); + $record = $processor($this->getRecord()); + + $this->assertSame(array('url' => 'B'), $record['extra']); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testInvalidData() + { + new WebProcessor(new \stdClass); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php b/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php new file mode 100644 index 00000000..ab899449 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.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 Monolog; + +use Monolog\Handler\TestHandler; +use Monolog\Formatter\LineFormatter; +use Monolog\Processor\PsrLogMessageProcessor; +use Psr\Log\Test\LoggerInterfaceTest; + +class PsrLogCompatTest extends LoggerInterfaceTest +{ + private $handler; + + public function getLogger() + { + $logger = new Logger('foo'); + $logger->pushHandler($handler = new TestHandler); + $logger->pushProcessor(new PsrLogMessageProcessor); + $handler->setFormatter(new LineFormatter('%level_name% %message%')); + + $this->handler = $handler; + + return $logger; + } + + public function getLogs() + { + $convert = function ($record) { + $lower = function ($match) { + return strtolower($match[0]); + }; + + return preg_replace_callback('{^[A-Z]+}', $lower, $record['formatted']); + }; + + return array_map($convert, $this->handler->getRecords()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/RegistryTest.php b/vendor/monolog/monolog/tests/Monolog/RegistryTest.php new file mode 100644 index 00000000..15fdfbd2 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/RegistryTest.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 Monolog; + +class RegistryTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + Registry::clear(); + } + + /** + * @dataProvider hasLoggerProvider + * @covers Monolog\Registry::hasLogger + */ + public function testHasLogger(array $loggersToAdd, array $loggersToCheck, array $expectedResult) + { + foreach ($loggersToAdd as $loggerToAdd) { + Registry::addLogger($loggerToAdd); + } + foreach ($loggersToCheck as $index => $loggerToCheck) { + $this->assertSame($expectedResult[$index], Registry::hasLogger($loggerToCheck)); + } + } + + public function hasLoggerProvider() + { + $logger1 = new Logger('test1'); + $logger2 = new Logger('test2'); + $logger3 = new Logger('test3'); + + return array( + // only instances + array( + array($logger1), + array($logger1, $logger2), + array(true, false), + ), + // only names + array( + array($logger1), + array('test1', 'test2'), + array(true, false), + ), + // mixed case + array( + array($logger1, $logger2), + array('test1', $logger2, 'test3', $logger3), + array(true, true, false, false), + ), + ); + } + + /** + * @covers Monolog\Registry::clear + */ + public function testClearClears() + { + Registry::addLogger(new Logger('test1'), 'log'); + Registry::clear(); + + $this->setExpectedException('\InvalidArgumentException'); + Registry::getInstance('log'); + } + + /** + * @dataProvider removedLoggerProvider + * @covers Monolog\Registry::addLogger + * @covers Monolog\Registry::removeLogger + */ + public function testRemovesLogger($loggerToAdd, $remove) + { + Registry::addLogger($loggerToAdd); + Registry::removeLogger($remove); + + $this->setExpectedException('\InvalidArgumentException'); + Registry::getInstance($loggerToAdd->getName()); + } + + public function removedLoggerProvider() + { + $logger1 = new Logger('test1'); + + return array( + array($logger1, $logger1), + array($logger1, 'test1'), + ); + } + + /** + * @covers Monolog\Registry::addLogger + * @covers Monolog\Registry::getInstance + * @covers Monolog\Registry::__callStatic + */ + public function testGetsSameLogger() + { + $logger1 = new Logger('test1'); + $logger2 = new Logger('test2'); + + Registry::addLogger($logger1, 'test1'); + Registry::addLogger($logger2); + + $this->assertSame($logger1, Registry::getInstance('test1')); + $this->assertSame($logger2, Registry::test2()); + } + + /** + * @expectedException \InvalidArgumentException + * @covers Monolog\Registry::getInstance + */ + public function testFailsOnNonExistantLogger() + { + Registry::getInstance('test1'); + } + + /** + * @covers Monolog\Registry::addLogger + */ + public function testReplacesLogger() + { + $log1 = new Logger('test1'); + $log2 = new Logger('test2'); + + Registry::addLogger($log1, 'log'); + + Registry::addLogger($log2, 'log', true); + + $this->assertSame($log2, Registry::getInstance('log')); + } + + /** + * @expectedException \InvalidArgumentException + * @covers Monolog\Registry::addLogger + */ + public function testFailsOnUnspecifiedReplacement() + { + $log1 = new Logger('test1'); + $log2 = new Logger('test2'); + + Registry::addLogger($log1, 'log'); + + Registry::addLogger($log2, 'log'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/TestCase.php b/vendor/monolog/monolog/tests/Monolog/TestCase.php new file mode 100644 index 00000000..4eb7b4c9 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/TestCase.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 Monolog; + +class TestCase extends \PHPUnit_Framework_TestCase +{ + /** + * @return array Record + */ + protected function getRecord($level = Logger::WARNING, $message = 'test', $context = array()) + { + return array( + 'message' => $message, + 'context' => $context, + 'level' => $level, + 'level_name' => Logger::getLevelName($level), + 'channel' => 'test', + 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true))), + 'extra' => array(), + ); + } + + /** + * @return array + */ + protected function getMultipleRecords() + { + return array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error'), + ); + } + + /** + * @return Monolog\Formatter\FormatterInterface + */ + protected function getIdentityFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { return $record['message']; })); + + return $formatter; + } +} diff --git a/vendor/paragonie/random_compat/LICENSE b/vendor/paragonie/random_compat/LICENSE new file mode 100644 index 00000000..45c7017d --- /dev/null +++ b/vendor/paragonie/random_compat/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Paragon Initiative Enterprises + +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/vendor/paragonie/random_compat/build-phar.sh b/vendor/paragonie/random_compat/build-phar.sh new file mode 100755 index 00000000..b4a5ba31 --- /dev/null +++ b/vendor/paragonie/random_compat/build-phar.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) ) + +php -dphar.readonly=0 "$basedir/other/build_phar.php" $* \ No newline at end of file diff --git a/vendor/paragonie/random_compat/composer.json b/vendor/paragonie/random_compat/composer.json new file mode 100644 index 00000000..1c5978c6 --- /dev/null +++ b/vendor/paragonie/random_compat/composer.json @@ -0,0 +1,37 @@ +{ + "name": "paragonie/random_compat", + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "random", + "pseudorandom" + ], + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "support": { + "issues": "https://github.com/paragonie/random_compat/issues", + "email": "info@paragonie.com", + "source": "https://github.com/paragonie/random_compat" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "autoload": { + "files": [ + "lib/random.php" + ] + } +} diff --git a/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey new file mode 100644 index 00000000..eb50ebfc --- /dev/null +++ b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm +pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p ++h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc +-----END PUBLIC KEY----- diff --git a/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc new file mode 100644 index 00000000..6a1d7f30 --- /dev/null +++ b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc @@ -0,0 +1,11 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v2.0.22 (MingW32) + +iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip +QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg +1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW +NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA +NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV +JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74= +=B6+8 +-----END PGP SIGNATURE----- diff --git a/vendor/paragonie/random_compat/lib/byte_safe_strings.php b/vendor/paragonie/random_compat/lib/byte_safe_strings.php new file mode 100644 index 00000000..3de86b22 --- /dev/null +++ b/vendor/paragonie/random_compat/lib/byte_safe_strings.php @@ -0,0 +1,181 @@ + RandomCompat_strlen($binary_string)) { + return ''; + } + + return (string) mb_substr($binary_string, $start, $length, '8bit'); + } + + } else { + + /** + * substr() implementation that isn't brittle to mbstring.func_overload + * + * This version just uses the default substr() + * + * @param string $binary_string + * @param int $start + * @param int $length (optional) + * + * @throws TypeError + * + * @return string + */ + function RandomCompat_substr($binary_string, $start, $length = null) + { + if (!is_string($binary_string)) { + throw new TypeError( + 'RandomCompat_substr(): First argument should be a string' + ); + } + + if (!is_int($start)) { + throw new TypeError( + 'RandomCompat_substr(): Second argument should be an integer' + ); + } + + if ($length !== null) { + if (!is_int($length)) { + throw new TypeError( + 'RandomCompat_substr(): Third argument should be an integer, or omitted' + ); + } + + return (string) substr($binary_string, $start, $length); + } + + return (string) substr($binary_string, $start); + } + } +} diff --git a/vendor/paragonie/random_compat/lib/cast_to_int.php b/vendor/paragonie/random_compat/lib/cast_to_int.php new file mode 100644 index 00000000..be7388df --- /dev/null +++ b/vendor/paragonie/random_compat/lib/cast_to_int.php @@ -0,0 +1,74 @@ + operators might accidentally let a float + * through. + * + * @param int|float $number The number we want to convert to an int + * @param boolean $fail_open Set to true to not throw an exception + * + * @return float|int + * + * @throws TypeError + */ + function RandomCompat_intval($number, $fail_open = false) + { + if (is_int($number) || is_float($number)) { + $number += 0; + } elseif (is_numeric($number)) { + $number += 0; + } + + if ( + is_float($number) + && + $number > ~PHP_INT_MAX + && + $number < PHP_INT_MAX + ) { + $number = (int) $number; + } + + if (is_int($number)) { + return (int) $number; + } elseif (!$fail_open) { + throw new TypeError( + 'Expected an integer.' + ); + } + return $number; + } +} diff --git a/vendor/paragonie/random_compat/lib/error_polyfill.php b/vendor/paragonie/random_compat/lib/error_polyfill.php new file mode 100644 index 00000000..6a91990c --- /dev/null +++ b/vendor/paragonie/random_compat/lib/error_polyfill.php @@ -0,0 +1,49 @@ += 70000) { + return; +} + +if (!defined('RANDOM_COMPAT_READ_BUFFER')) { + define('RANDOM_COMPAT_READ_BUFFER', 8); +} + +$RandomCompatDIR = dirname(__FILE__); + +require_once $RandomCompatDIR . '/byte_safe_strings.php'; +require_once $RandomCompatDIR . '/cast_to_int.php'; +require_once $RandomCompatDIR . '/error_polyfill.php'; + +if (!is_callable('random_bytes')) { + /** + * PHP 5.2.0 - 5.6.x way to implement random_bytes() + * + * We use conditional statements here to define the function in accordance + * to the operating environment. It's a micro-optimization. + * + * In order of preference: + * 1. Use libsodium if available. + * 2. fread() /dev/urandom if available (never on Windows) + * 3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM) + * 4. COM('CAPICOM.Utilities.1')->GetRandom() + * + * See RATIONALE.md for our reasoning behind this particular order + */ + if (extension_loaded('libsodium')) { + // See random_bytes_libsodium.php + if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) { + require_once $RandomCompatDIR . '/random_bytes_libsodium.php'; + } elseif (method_exists('Sodium', 'randombytes_buf')) { + require_once $RandomCompatDIR . '/random_bytes_libsodium_legacy.php'; + } + } + + /** + * Reading directly from /dev/urandom: + */ + if (DIRECTORY_SEPARATOR === '/') { + // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast + // way to exclude Windows. + $RandomCompatUrandom = true; + $RandomCompat_basedir = ini_get('open_basedir'); + + if (!empty($RandomCompat_basedir)) { + $RandomCompat_open_basedir = explode( + PATH_SEPARATOR, + strtolower($RandomCompat_basedir) + ); + $RandomCompatUrandom = (array() !== array_intersect( + array('/dev', '/dev/', '/dev/urandom'), + $RandomCompat_open_basedir + )); + $RandomCompat_open_basedir = null; + } + + if ( + !is_callable('random_bytes') + && + $RandomCompatUrandom + && + @is_readable('/dev/urandom') + ) { + // Error suppression on is_readable() in case of an open_basedir + // or safe_mode failure. All we care about is whether or not we + // can read it at this point. If the PHP environment is going to + // panic over trying to see if the file can be read in the first + // place, that is not helpful to us here. + + // See random_bytes_dev_urandom.php + require_once $RandomCompatDIR . '/random_bytes_dev_urandom.php'; + } + // Unset variables after use + $RandomCompat_basedir = null; + } else { + $RandomCompatUrandom = false; + } + + /** + * mcrypt_create_iv() + * + * We only want to use mcypt_create_iv() if: + * + * - random_bytes() hasn't already been defined + * - the mcrypt extensions is loaded + * - One of these two conditions is true: + * - We're on Windows (DIRECTORY_SEPARATOR !== '/') + * - We're not on Windows and /dev/urandom is readabale + * (i.e. we're not in a chroot jail) + * - Special case: + * - If we're not on Windows, but the PHP version is between + * 5.6.10 and 5.6.12, we don't want to use mcrypt. It will + * hang indefinitely. This is bad. + * - If we're on Windows, we want to use PHP >= 5.3.7 or else + * we get insufficient entropy errors. + */ + if ( + !is_callable('random_bytes') + && + // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be. + (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307) + && + // Prevent this code from hanging indefinitely on non-Windows; + // see https://bugs.php.net/bug.php?id=69833 + ( + DIRECTORY_SEPARATOR !== '/' || + (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613) + ) + && + extension_loaded('mcrypt') + ) { + // See random_bytes_mcrypt.php + require_once $RandomCompatDIR . '/random_bytes_mcrypt.php'; + } + $RandomCompatUrandom = null; + + /** + * This is a Windows-specific fallback, for when the mcrypt extension + * isn't loaded. + */ + if ( + !is_callable('random_bytes') + && + extension_loaded('com_dotnet') + && + class_exists('COM') + ) { + $RandomCompat_disabled_classes = preg_split( + '#\s*,\s*#', + strtolower(ini_get('disable_classes')) + ); + + if (!in_array('com', $RandomCompat_disabled_classes)) { + try { + $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1'); + if (method_exists($RandomCompatCOMtest, 'GetRandom')) { + // See random_bytes_com_dotnet.php + require_once $RandomCompatDIR . '/random_bytes_com_dotnet.php'; + } + } catch (com_exception $e) { + // Don't try to use it. + } + } + $RandomCompat_disabled_classes = null; + $RandomCompatCOMtest = null; + } + + /** + * throw new Exception + */ + if (!is_callable('random_bytes')) { + /** + * We don't have any more options, so let's throw an exception right now + * and hope the developer won't let it fail silently. + * + * @param mixed $length + * @return void + * @throws Exception + */ + function random_bytes($length) + { + unset($length); // Suppress "variable not used" warnings. + throw new Exception( + 'There is no suitable CSPRNG installed on your system' + ); + } + } +} + +if (!is_callable('random_int')) { + require_once $RandomCompatDIR . '/random_int.php'; +} + +$RandomCompatDIR = null; diff --git a/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php b/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php new file mode 100644 index 00000000..fc1926e5 --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php @@ -0,0 +1,88 @@ +GetRandom($bytes, 0)); + if (RandomCompat_strlen($buf) >= $bytes) { + /** + * Return our random entropy buffer here: + */ + return RandomCompat_substr($buf, 0, $bytes); + } + ++$execCount; + } while ($execCount < $bytes); + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Could not gather sufficient random data' + ); + } +} \ No newline at end of file diff --git a/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php b/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php new file mode 100644 index 00000000..df5b9152 --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php @@ -0,0 +1,167 @@ + 0); + + /** + * Is our result valid? + */ + if (is_string($buf)) { + if (RandomCompat_strlen($buf) === $bytes) { + /** + * Return our random entropy buffer here: + */ + return $buf; + } + } + } + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Error reading from source device' + ); + } +} diff --git a/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php b/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php new file mode 100644 index 00000000..4af1a242 --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php @@ -0,0 +1,88 @@ + 2147483647) { + $buf = ''; + for ($i = 0; $i < $bytes; $i += 1073741824) { + $n = ($bytes - $i) > 1073741824 + ? 1073741824 + : $bytes - $i; + $buf .= \Sodium\randombytes_buf($n); + } + } else { + $buf = \Sodium\randombytes_buf($bytes); + } + + if ($buf !== false) { + if (RandomCompat_strlen($buf) === $bytes) { + return $buf; + } + } + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Could not gather sufficient random data' + ); + } +} diff --git a/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php b/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php new file mode 100644 index 00000000..02160b91 --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php @@ -0,0 +1,92 @@ + 2147483647) { + for ($i = 0; $i < $bytes; $i += 1073741824) { + $n = ($bytes - $i) > 1073741824 + ? 1073741824 + : $bytes - $i; + $buf .= Sodium::randombytes_buf($n); + } + } else { + $buf .= Sodium::randombytes_buf($bytes); + } + + if (is_string($buf)) { + if (RandomCompat_strlen($buf) === $bytes) { + return $buf; + } + } + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Could not gather sufficient random data' + ); + } +} diff --git a/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php b/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php new file mode 100644 index 00000000..aac9c013 --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php @@ -0,0 +1,77 @@ + operators might accidentally let a float + * through. + */ + + try { + $min = RandomCompat_intval($min); + } catch (TypeError $ex) { + throw new TypeError( + 'random_int(): $min must be an integer' + ); + } + + try { + $max = RandomCompat_intval($max); + } catch (TypeError $ex) { + throw new TypeError( + 'random_int(): $max must be an integer' + ); + } + + /** + * Now that we've verified our weak typing system has given us an integer, + * let's validate the logic then we can move forward with generating random + * integers along a given range. + */ + if ($min > $max) { + throw new Error( + 'Minimum value must be less than or equal to the maximum value' + ); + } + + if ($max === $min) { + return $min; + } + + /** + * Initialize variables to 0 + * + * We want to store: + * $bytes => the number of random bytes we need + * $mask => an integer bitmask (for use with the &) operator + * so we can minimize the number of discards + */ + $attempts = $bits = $bytes = $mask = $valueShift = 0; + + /** + * At this point, $range is a positive number greater than 0. It might + * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to + * a float and we will lose some precision. + */ + $range = $max - $min; + + /** + * Test for integer overflow: + */ + if (!is_int($range)) { + + /** + * Still safely calculate wider ranges. + * Provided by @CodesInChaos, @oittaa + * + * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435 + * + * We use ~0 as a mask in this case because it generates all 1s + * + * @ref https://eval.in/400356 (32-bit) + * @ref http://3v4l.org/XX9r5 (64-bit) + */ + $bytes = PHP_INT_SIZE; + $mask = ~0; + + } else { + + /** + * $bits is effectively ceil(log($range, 2)) without dealing with + * type juggling + */ + while ($range > 0) { + if ($bits % 8 === 0) { + ++$bytes; + } + ++$bits; + $range >>= 1; + $mask = $mask << 1 | 1; + } + $valueShift = $min; + } + + $val = 0; + /** + * Now that we have our parameters set up, let's begin generating + * random integers until one falls between $min and $max + */ + do { + /** + * The rejection probability is at most 0.5, so this corresponds + * to a failure probability of 2^-128 for a working RNG + */ + if ($attempts > 128) { + throw new Exception( + 'random_int: RNG is broken - too many rejections' + ); + } + + /** + * Let's grab the necessary number of random bytes + */ + $randomByteString = random_bytes($bytes); + + /** + * Let's turn $randomByteString into an integer + * + * This uses bitwise operators (<< and |) to build an integer + * out of the values extracted from ord() + * + * Example: [9F] | [6D] | [32] | [0C] => + * 159 + 27904 + 3276800 + 201326592 => + * 204631455 + */ + $val &= 0; + for ($i = 0; $i < $bytes; ++$i) { + $val |= ord($randomByteString[$i]) << ($i * 8); + } + + /** + * Apply mask + */ + $val &= $mask; + $val += $valueShift; + + ++$attempts; + /** + * If $val overflows to a floating point number, + * ... or is larger than $max, + * ... or smaller than $min, + * then try again. + */ + } while (!is_int($val) || $val > $max || $val < $min); + + return (int)$val; + } +} diff --git a/vendor/paragonie/random_compat/other/build_phar.php b/vendor/paragonie/random_compat/other/build_phar.php new file mode 100644 index 00000000..70ef4b2e --- /dev/null +++ b/vendor/paragonie/random_compat/other/build_phar.php @@ -0,0 +1,57 @@ +buildFromDirectory(dirname(__DIR__).'/lib'); +rename( + dirname(__DIR__).'/lib/index.php', + dirname(__DIR__).'/lib/random.php' +); + +/** + * If we pass an (optional) path to a private key as a second argument, we will + * sign the Phar with OpenSSL. + * + * If you leave this out, it will produce an unsigned .phar! + */ +if ($argc > 1) { + if (!@is_readable($argv[1])) { + echo 'Could not read the private key file:', $argv[1], "\n"; + exit(255); + } + $pkeyFile = file_get_contents($argv[1]); + + $private = openssl_get_privatekey($pkeyFile); + if ($private !== false) { + $pkey = ''; + openssl_pkey_export($private, $pkey); + $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey); + + /** + * Save the corresponding public key to the file + */ + if (!@is_readable($dist.'/random_compat.phar.pubkey')) { + $details = openssl_pkey_get_details($private); + file_put_contents( + $dist.'/random_compat.phar.pubkey', + $details['key'] + ); + } + } else { + echo 'An error occurred reading the private key from OpenSSL.', "\n"; + exit(255); + } +} diff --git a/vendor/paragonie/random_compat/psalm-autoload.php b/vendor/paragonie/random_compat/psalm-autoload.php new file mode 100644 index 00000000..d71d1b81 --- /dev/null +++ b/vendor/paragonie/random_compat/psalm-autoload.php @@ -0,0 +1,9 @@ + + + + + + + + + + + diff --git a/vendor/pimple/pimple/.gitignore b/vendor/pimple/pimple/.gitignore new file mode 100644 index 00000000..c089b095 --- /dev/null +++ b/vendor/pimple/pimple/.gitignore @@ -0,0 +1,3 @@ +phpunit.xml +composer.lock +/vendor/ diff --git a/vendor/pimple/pimple/.travis.yml b/vendor/pimple/pimple/.travis.yml new file mode 100644 index 00000000..5f8bb7c9 --- /dev/null +++ b/vendor/pimple/pimple/.travis.yml @@ -0,0 +1,32 @@ +language: php + +env: + matrix: + - PIMPLE_EXT=no + - PIMPLE_EXT=yes + global: + - REPORT_EXIT_STATUS=1 + +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - hhvm + +before_script: + - composer self-update + - COMPOSER_ROOT_VERSION=dev-master composer dump-autoload + - if [ "$PIMPLE_EXT" == "yes" ]; then sh -c "cd ext/pimple && phpize && ./configure && make && sudo make install"; fi + - if [ "$PIMPLE_EXT" == "yes" ]; then echo "extension=pimple.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`; fi + +script: + - cd ext/pimple + - if [ "$PIMPLE_EXT" == "yes" ]; then yes n | make test | tee output ; grep -E 'Tests failed +. +0' output; fi + - cd ../.. + - phpunit + +matrix: + exclude: + - php: hhvm + env: PIMPLE_EXT=yes diff --git a/vendor/pimple/pimple/CHANGELOG b/vendor/pimple/pimple/CHANGELOG new file mode 100644 index 00000000..cc679972 --- /dev/null +++ b/vendor/pimple/pimple/CHANGELOG @@ -0,0 +1,35 @@ +* 3.0.2 (2015-09-11) + + * refactored the C extension + * minor non-significant changes + +* 3.0.1 (2015-07-30) + + * simplified some code + * fixed a segfault in the C extension + +* 3.0.0 (2014-07-24) + + * removed the Pimple class alias (use Pimple\Container instead) + +* 2.1.1 (2014-07-24) + + * fixed compiler warnings for the C extension + * fixed code when dealing with circular references + +* 2.1.0 (2014-06-24) + + * moved the Pimple to Pimple\Container (with a BC layer -- Pimple is now a + deprecated alias which will be removed in Pimple 3.0) + * added Pimple\ServiceProviderInterface (and Pimple::register()) + +* 2.0.0 (2014-02-10) + + * changed extend to automatically re-assign the extended service and keep it as shared or factory + (to keep BC, extend still returns the extended service) + * changed services to be shared by default (use factory() for factory + services) + +* 1.0.0 + + * initial version diff --git a/vendor/pimple/pimple/LICENSE b/vendor/pimple/pimple/LICENSE new file mode 100644 index 00000000..d7949e2f --- /dev/null +++ b/vendor/pimple/pimple/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2009-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/vendor/pimple/pimple/README.rst b/vendor/pimple/pimple/README.rst new file mode 100644 index 00000000..93fb35a8 --- /dev/null +++ b/vendor/pimple/pimple/README.rst @@ -0,0 +1,201 @@ +Pimple +====== + +.. caution:: + + This is the documentation for Pimple 3.x. If you are using Pimple 1.x, read + the `Pimple 1.x documentation`_. Reading the Pimple 1.x code is also a good + way to learn more about how to create a simple Dependency Injection + Container (recent versions of Pimple are more focused on performance). + +Pimple is a small Dependency Injection Container for PHP. + +Installation +------------ + +Before using Pimple in your project, add it to your ``composer.json`` file: + +.. code-block:: bash + + $ ./composer.phar require pimple/pimple ~3.0 + +Alternatively, Pimple is also available as a PHP C extension: + +.. code-block:: bash + + $ git clone https://github.com/silexphp/Pimple + $ cd Pimple/ext/pimple + $ phpize + $ ./configure + $ make + $ make install + +Usage +----- + +Creating a container is a matter of creating a ``Container`` instance: + +.. code-block:: php + + use Pimple\Container; + + $container = new Container(); + +As many other dependency injection containers, Pimple manages two different +kind of data: **services** and **parameters**. + +Defining Services +~~~~~~~~~~~~~~~~~ + +A service is an object that does something as part of a larger system. Examples +of services: a database connection, a templating engine, or a mailer. Almost +any **global** object can be a service. + +Services are defined by **anonymous functions** that return an instance of an +object: + +.. code-block:: php + + // define some services + $container['session_storage'] = function ($c) { + return new SessionStorage('SESSION_ID'); + }; + + $container['session'] = function ($c) { + return new Session($c['session_storage']); + }; + +Notice that the anonymous function has access to the current container +instance, allowing references to other services or parameters. + +As objects are only created when you get them, the order of the definitions +does not matter. + +Using the defined services is also very easy: + +.. code-block:: php + + // get the session object + $session = $container['session']; + + // the above call is roughly equivalent to the following code: + // $storage = new SessionStorage('SESSION_ID'); + // $session = new Session($storage); + +Defining Factory Services +~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, each time you get a service, Pimple returns the **same instance** +of it. If you want a different instance to be returned for all calls, wrap your +anonymous function with the ``factory()`` method + +.. code-block:: php + + $container['session'] = $container->factory(function ($c) { + return new Session($c['session_storage']); + }); + +Now, each call to ``$container['session']`` returns a new instance of the +session. + +Defining Parameters +~~~~~~~~~~~~~~~~~~~ + +Defining a parameter allows to ease the configuration of your container from +the outside and to store global values: + +.. code-block:: php + + // define some parameters + $container['cookie_name'] = 'SESSION_ID'; + $container['session_storage_class'] = 'SessionStorage'; + +If you change the ``session_storage`` service definition like below: + +.. code-block:: php + + $container['session_storage'] = function ($c) { + return new $c['session_storage_class']($c['cookie_name']); + }; + +You can now easily change the cookie name by overriding the +``session_storage_class`` parameter instead of redefining the service +definition. + +Protecting Parameters +~~~~~~~~~~~~~~~~~~~~~ + +Because Pimple sees anonymous functions as service definitions, you need to +wrap anonymous functions with the ``protect()`` method to store them as +parameters: + +.. code-block:: php + + $container['random_func'] = $container->protect(function () { + return rand(); + }); + +Modifying Services after Definition +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In some cases you may want to modify a service definition after it has been +defined. You can use the ``extend()`` method to define additional code to be +run on your service just after it is created: + +.. code-block:: php + + $container['session_storage'] = function ($c) { + return new $c['session_storage_class']($c['cookie_name']); + }; + + $container->extend('session_storage', function ($storage, $c) { + $storage->...(); + + return $storage; + }); + +The first argument is the name of the service to extend, the second a function +that gets access to the object instance and the container. + +Extending a Container +~~~~~~~~~~~~~~~~~~~~~ + +If you use the same libraries over and over, you might want to reuse some +services from one project to the next one; package your services into a +**provider** by implementing ``Pimple\ServiceProviderInterface``: + +.. code-block:: php + + use Pimple\Container; + + class FooProvider implements Pimple\ServiceProviderInterface + { + public function register(Container $pimple) + { + // register some services and parameters + // on $pimple + } + } + +Then, register the provider on a Container: + +.. code-block:: php + + $pimple->register(new FooProvider()); + +Fetching the Service Creation Function +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When you access an object, Pimple automatically calls the anonymous function +that you defined, which creates the service object for you. If you want to get +raw access to this function, you can use the ``raw()`` method: + +.. code-block:: php + + $container['session'] = function ($c) { + return new Session($c['session_storage']); + }; + + $sessionFunction = $container->raw('session'); + +.. _Pimple 1.x documentation: https://github.com/silexphp/Pimple/tree/1.1 diff --git a/vendor/pimple/pimple/composer.json b/vendor/pimple/pimple/composer.json new file mode 100644 index 00000000..a5268f16 --- /dev/null +++ b/vendor/pimple/pimple/composer.json @@ -0,0 +1,25 @@ +{ + "name": "pimple/pimple", + "type": "library", + "description": "Pimple, a simple Dependency Injection Container", + "keywords": ["dependency injection", "container"], + "homepage": "http://pimple.sensiolabs.org", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-0": { "Pimple": "src/" } + }, + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + } +} diff --git a/vendor/pimple/pimple/ext/pimple/.gitignore b/vendor/pimple/pimple/ext/pimple/.gitignore new file mode 100644 index 00000000..1861088a --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/.gitignore @@ -0,0 +1,30 @@ +*.sw* +.deps +Makefile +Makefile.fragments +Makefile.global +Makefile.objects +acinclude.m4 +aclocal.m4 +build/ +config.cache +config.guess +config.h +config.h.in +config.log +config.nice +config.status +config.sub +configure +configure.in +install-sh +libtool +ltmain.sh +missing +mkinstalldirs +run-tests.php +*.loT +.libs/ +modules/ +*.la +*.lo diff --git a/vendor/pimple/pimple/ext/pimple/README.md b/vendor/pimple/pimple/ext/pimple/README.md new file mode 100644 index 00000000..7b39eb29 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/README.md @@ -0,0 +1,12 @@ +This is Pimple 2 implemented in C + +* PHP >= 5.3 +* Not tested under Windows, might work + +Install +======= + + > phpize + > ./configure + > make + > make install diff --git a/vendor/pimple/pimple/ext/pimple/config.m4 b/vendor/pimple/pimple/ext/pimple/config.m4 new file mode 100644 index 00000000..c9ba17dd --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/config.m4 @@ -0,0 +1,63 @@ +dnl $Id$ +dnl config.m4 for extension pimple + +dnl Comments in this file start with the string 'dnl'. +dnl Remove where necessary. This file will not work +dnl without editing. + +dnl If your extension references something external, use with: + +dnl PHP_ARG_WITH(pimple, for pimple support, +dnl Make sure that the comment is aligned: +dnl [ --with-pimple Include pimple support]) + +dnl Otherwise use enable: + +PHP_ARG_ENABLE(pimple, whether to enable pimple support, +dnl Make sure that the comment is aligned: +[ --enable-pimple Enable pimple support]) + +if test "$PHP_PIMPLE" != "no"; then + dnl Write more examples of tests here... + + dnl # --with-pimple -> check with-path + dnl SEARCH_PATH="/usr/local /usr" # you might want to change this + dnl SEARCH_FOR="/include/pimple.h" # you most likely want to change this + dnl if test -r $PHP_PIMPLE/$SEARCH_FOR; then # path given as parameter + dnl PIMPLE_DIR=$PHP_PIMPLE + dnl else # search default path list + dnl AC_MSG_CHECKING([for pimple files in default path]) + dnl for i in $SEARCH_PATH ; do + dnl if test -r $i/$SEARCH_FOR; then + dnl PIMPLE_DIR=$i + dnl AC_MSG_RESULT(found in $i) + dnl fi + dnl done + dnl fi + dnl + dnl if test -z "$PIMPLE_DIR"; then + dnl AC_MSG_RESULT([not found]) + dnl AC_MSG_ERROR([Please reinstall the pimple distribution]) + dnl fi + + dnl # --with-pimple -> add include path + dnl PHP_ADD_INCLUDE($PIMPLE_DIR/include) + + dnl # --with-pimple -> check for lib and symbol presence + dnl LIBNAME=pimple # you may want to change this + dnl LIBSYMBOL=pimple # you most likely want to change this + + dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, + dnl [ + dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $PIMPLE_DIR/lib, PIMPLE_SHARED_LIBADD) + dnl AC_DEFINE(HAVE_PIMPLELIB,1,[ ]) + dnl ],[ + dnl AC_MSG_ERROR([wrong pimple lib version or lib not found]) + dnl ],[ + dnl -L$PIMPLE_DIR/lib -lm + dnl ]) + dnl + dnl PHP_SUBST(PIMPLE_SHARED_LIBADD) + + PHP_NEW_EXTENSION(pimple, pimple.c, $ext_shared) +fi diff --git a/vendor/pimple/pimple/ext/pimple/config.w32 b/vendor/pimple/pimple/ext/pimple/config.w32 new file mode 100644 index 00000000..39857b32 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/config.w32 @@ -0,0 +1,13 @@ +// $Id$ +// vim:ft=javascript + +// If your extension references something external, use ARG_WITH +// ARG_WITH("pimple", "for pimple support", "no"); + +// Otherwise, use ARG_ENABLE +// ARG_ENABLE("pimple", "enable pimple support", "no"); + +if (PHP_PIMPLE != "no") { + EXTENSION("pimple", "pimple.c"); +} + diff --git a/vendor/pimple/pimple/ext/pimple/php_pimple.h b/vendor/pimple/pimple/ext/pimple/php_pimple.h new file mode 100644 index 00000000..49431f08 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/php_pimple.h @@ -0,0 +1,121 @@ + +/* + * This file is part of Pimple. + * + * Copyright (c) 2014 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. + */ + +#ifndef PHP_PIMPLE_H +#define PHP_PIMPLE_H + +extern zend_module_entry pimple_module_entry; +#define phpext_pimple_ptr &pimple_module_entry + +#ifdef PHP_WIN32 +# define PHP_PIMPLE_API __declspec(dllexport) +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_PIMPLE_API __attribute__ ((visibility("default"))) +#else +# define PHP_PIMPLE_API +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +#define PIMPLE_VERSION "3.0.2" +#define PIMPLE_NS "Pimple" + +#define PIMPLE_DEFAULT_ZVAL_CACHE_NUM 5 +#define PIMPLE_DEFAULT_ZVAL_VALUES_NUM 10 + +zend_module_entry *get_module(void); + +PHP_MINIT_FUNCTION(pimple); +PHP_MINFO_FUNCTION(pimple); + +PHP_METHOD(Pimple, __construct); +PHP_METHOD(Pimple, factory); +PHP_METHOD(Pimple, protect); +PHP_METHOD(Pimple, raw); +PHP_METHOD(Pimple, extend); +PHP_METHOD(Pimple, keys); +PHP_METHOD(Pimple, register); +PHP_METHOD(Pimple, offsetSet); +PHP_METHOD(Pimple, offsetUnset); +PHP_METHOD(Pimple, offsetGet); +PHP_METHOD(Pimple, offsetExists); + +PHP_METHOD(PimpleClosure, invoker); + +typedef struct _pimple_bucket_value { + zval *value; /* Must be the first element */ + zval *raw; + zend_object_handle handle_num; + enum { + PIMPLE_IS_PARAM = 0, + PIMPLE_IS_SERVICE = 2 + } type; + zend_bool initialized; + zend_fcall_info_cache fcc; +} pimple_bucket_value; + +typedef struct _pimple_object { + zend_object zobj; + HashTable values; + HashTable factories; + HashTable protected; +} pimple_object; + +typedef struct _pimple_closure_object { + zend_object zobj; + zval *callable; + zval *factory; +} pimple_closure_object; + +static const char sensiolabs_logo[] = ""; + +static int pimple_zval_to_pimpleval(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC); +static int pimple_zval_is_valid_callback(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC); + +static void pimple_bucket_dtor(pimple_bucket_value *bucket); +static void pimple_free_bucket(pimple_bucket_value *bucket); + +static zval *pimple_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC); +static void pimple_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC); +static int pimple_object_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC); +static void pimple_object_unset_dimension(zval *object, zval *offset TSRMLS_DC); +static zend_object_value pimple_object_create(zend_class_entry *ce TSRMLS_DC); +static void pimple_free_object_storage(pimple_object *obj TSRMLS_DC); + +static void pimple_closure_free_object_storage(pimple_closure_object *obj TSRMLS_DC); +static zend_object_value pimple_closure_object_create(zend_class_entry *ce TSRMLS_DC); +static zend_function *pimple_closure_get_constructor(zval * TSRMLS_DC); +static int pimple_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, union _zend_function **fptr_ptr, zval **zobj_ptr TSRMLS_DC); + +#ifdef ZTS +#define PIMPLE_G(v) TSRMG(pimple_globals_id, zend_pimple_globals *, v) +#else +#define PIMPLE_G(v) (pimple_globals.v) +#endif + +#endif /* PHP_PIMPLE_H */ + diff --git a/vendor/pimple/pimple/ext/pimple/pimple.c b/vendor/pimple/pimple/ext/pimple/pimple.c new file mode 100644 index 00000000..239c01d6 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/pimple.c @@ -0,0 +1,922 @@ + +/* + * This file is part of Pimple. + * + * Copyright (c) 2014 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_pimple.h" +#include "pimple_compat.h" +#include "zend_interfaces.h" +#include "zend.h" +#include "Zend/zend_closures.h" +#include "ext/spl/spl_exceptions.h" +#include "Zend/zend_exceptions.h" +#include "main/php_output.h" +#include "SAPI.h" + +static zend_class_entry *pimple_ce; +static zend_object_handlers pimple_object_handlers; +static zend_class_entry *pimple_closure_ce; +static zend_class_entry *pimple_serviceprovider_ce; +static zend_object_handlers pimple_closure_object_handlers; +static zend_internal_function pimple_closure_invoker_function; + +#define FETCH_DIM_HANDLERS_VARS pimple_object *pimple_obj = NULL; \ + ulong index; \ + pimple_obj = (pimple_object *)zend_object_store_get_object(object TSRMLS_CC); \ + +#define PIMPLE_OBJECT_HANDLE_INHERITANCE_OBJECT_HANDLERS do { \ + if (ce != pimple_ce) { \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetget"), (void **)&function); \ + if (function->common.scope != ce) { /* if the function is not defined in this actual class */ \ + pimple_object_handlers.read_dimension = pimple_object_read_dimension; /* then overwrite the handler to use custom one */ \ + } \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetset"), (void **)&function); \ + if (function->common.scope != ce) { \ + pimple_object_handlers.write_dimension = pimple_object_write_dimension; \ + } \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetexists"), (void **)&function); \ + if (function->common.scope != ce) { \ + pimple_object_handlers.has_dimension = pimple_object_has_dimension; \ + } \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetunset"), (void **)&function); \ + if (function->common.scope != ce) { \ + pimple_object_handlers.unset_dimension = pimple_object_unset_dimension; \ + } \ + } else { \ + pimple_object_handlers.read_dimension = pimple_object_read_dimension; \ + pimple_object_handlers.write_dimension = pimple_object_write_dimension; \ + pimple_object_handlers.has_dimension = pimple_object_has_dimension; \ + pimple_object_handlers.unset_dimension = pimple_object_unset_dimension; \ + }\ + } while(0); + +#define PIMPLE_CALL_CB do { \ + zend_fcall_info_argn(&fci TSRMLS_CC, 1, &object); \ + fci.size = sizeof(fci); \ + fci.object_ptr = retval->fcc.object_ptr; \ + fci.function_name = retval->value; \ + fci.no_separation = 1; \ + fci.retval_ptr_ptr = &retval_ptr_ptr; \ +\ + zend_call_function(&fci, &retval->fcc TSRMLS_CC); \ + efree(fci.params); \ + if (EG(exception)) { \ + return EG(uninitialized_zval_ptr); \ + } \ + } while(0); + +ZEND_BEGIN_ARG_INFO_EX(arginfo___construct, 0, 0, 0) +ZEND_ARG_ARRAY_INFO(0, value, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetset, 0, 0, 2) +ZEND_ARG_INFO(0, offset) +ZEND_ARG_INFO(0, value) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetget, 0, 0, 1) +ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetexists, 0, 0, 1) +ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetunset, 0, 0, 1) +ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_factory, 0, 0, 1) +ZEND_ARG_INFO(0, callable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_protect, 0, 0, 1) +ZEND_ARG_INFO(0, callable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_raw, 0, 0, 1) +ZEND_ARG_INFO(0, id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_extend, 0, 0, 2) +ZEND_ARG_INFO(0, id) +ZEND_ARG_INFO(0, callable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_keys, 0, 0, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_register, 0, 0, 1) +ZEND_ARG_OBJ_INFO(0, provider, Pimple\\ServiceProviderInterface, 0) +ZEND_ARG_ARRAY_INFO(0, values, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_serviceprovider_register, 0, 0, 1) +ZEND_ARG_OBJ_INFO(0, pimple, Pimple\\Container, 0) +ZEND_END_ARG_INFO() + +static const zend_function_entry pimple_ce_functions[] = { + PHP_ME(Pimple, __construct, arginfo___construct, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, factory, arginfo_factory, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, protect, arginfo_protect, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, raw, arginfo_raw, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, extend, arginfo_extend, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, keys, arginfo_keys, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, register, arginfo_register, ZEND_ACC_PUBLIC) + + PHP_ME(Pimple, offsetSet, arginfo_offsetset, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, offsetGet, arginfo_offsetget, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, offsetExists, arginfo_offsetexists, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, offsetUnset, arginfo_offsetunset, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static const zend_function_entry pimple_serviceprovider_iface_ce_functions[] = { + PHP_ABSTRACT_ME(ServiceProviderInterface, register, arginfo_serviceprovider_register) + PHP_FE_END +}; + +static void pimple_closure_free_object_storage(pimple_closure_object *obj TSRMLS_DC) +{ + zend_object_std_dtor(&obj->zobj TSRMLS_CC); + if (obj->factory) { + zval_ptr_dtor(&obj->factory); + } + if (obj->callable) { + zval_ptr_dtor(&obj->callable); + } + efree(obj); +} + +static void pimple_free_object_storage(pimple_object *obj TSRMLS_DC) +{ + zend_hash_destroy(&obj->factories); + zend_hash_destroy(&obj->protected); + zend_hash_destroy(&obj->values); + zend_object_std_dtor(&obj->zobj TSRMLS_CC); + efree(obj); +} + +static void pimple_free_bucket(pimple_bucket_value *bucket) +{ + if (bucket->raw) { + zval_ptr_dtor(&bucket->raw); + } +} + +static zend_object_value pimple_closure_object_create(zend_class_entry *ce TSRMLS_DC) +{ + zend_object_value retval; + pimple_closure_object *pimple_closure_obj = NULL; + + pimple_closure_obj = ecalloc(1, sizeof(pimple_closure_object)); + ZEND_OBJ_INIT(&pimple_closure_obj->zobj, ce); + + pimple_closure_object_handlers.get_constructor = pimple_closure_get_constructor; + retval.handlers = &pimple_closure_object_handlers; + retval.handle = zend_objects_store_put(pimple_closure_obj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) pimple_closure_free_object_storage, NULL TSRMLS_CC); + + return retval; +} + +static zend_function *pimple_closure_get_constructor(zval *obj TSRMLS_DC) +{ + zend_error(E_ERROR, "Pimple\\ContainerClosure is an internal class and cannot be instantiated"); + + return NULL; +} + +static int pimple_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, union _zend_function **fptr_ptr, zval **zobj_ptr TSRMLS_DC) +{ + *zobj_ptr = obj; + *ce_ptr = Z_OBJCE_P(obj); + *fptr_ptr = (zend_function *)&pimple_closure_invoker_function; + + return SUCCESS; +} + +static zend_object_value pimple_object_create(zend_class_entry *ce TSRMLS_DC) +{ + zend_object_value retval; + pimple_object *pimple_obj = NULL; + zend_function *function = NULL; + + pimple_obj = emalloc(sizeof(pimple_object)); + ZEND_OBJ_INIT(&pimple_obj->zobj, ce); + + PIMPLE_OBJECT_HANDLE_INHERITANCE_OBJECT_HANDLERS + + retval.handlers = &pimple_object_handlers; + retval.handle = zend_objects_store_put(pimple_obj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) pimple_free_object_storage, NULL TSRMLS_CC); + + zend_hash_init(&pimple_obj->factories, PIMPLE_DEFAULT_ZVAL_CACHE_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0); + zend_hash_init(&pimple_obj->protected, PIMPLE_DEFAULT_ZVAL_CACHE_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0); + zend_hash_init(&pimple_obj->values, PIMPLE_DEFAULT_ZVAL_VALUES_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0); + + return retval; +} + +static void pimple_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + pimple_bucket_value pimple_value = {0}, *found_value = NULL; + ulong hash; + + pimple_zval_to_pimpleval(value, &pimple_value TSRMLS_CC); + + if (!offset) {/* $p[] = 'foo' when not overloaded */ + zend_hash_next_index_insert(&pimple_obj->values, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL); + Z_ADDREF_P(value); + return; + } + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + hash = zend_hash_func(Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + zend_hash_quick_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, hash, (void **)&found_value); + if (found_value && found_value->type == PIMPLE_IS_SERVICE && found_value->initialized == 1) { + pimple_free_bucket(&pimple_value); + zend_throw_exception_ex(spl_ce_RuntimeException, 0 TSRMLS_CC, "Cannot override frozen service \"%s\".", Z_STRVAL_P(offset)); + return; + } + if (zend_hash_quick_update(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, hash, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL) == FAILURE) { + pimple_free_bucket(&pimple_value); + return; + } + Z_ADDREF_P(value); + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + zend_hash_index_find(&pimple_obj->values, index, (void **)&found_value); + if (found_value && found_value->type == PIMPLE_IS_SERVICE && found_value->initialized == 1) { + pimple_free_bucket(&pimple_value); + zend_throw_exception_ex(spl_ce_RuntimeException, 0 TSRMLS_CC, "Cannot override frozen service \"%ld\".", index); + return; + } + if (zend_hash_index_update(&pimple_obj->values, index, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL) == FAILURE) { + pimple_free_bucket(&pimple_value); + return; + } + Z_ADDREF_P(value); + break; + case IS_NULL: /* $p[] = 'foo' when overloaded */ + zend_hash_next_index_insert(&pimple_obj->values, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL); + Z_ADDREF_P(value); + break; + default: + pimple_free_bucket(&pimple_value); + zend_error(E_WARNING, "Unsupported offset type"); + } +} + +static void pimple_object_unset_dimension(zval *object, zval *offset TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + zend_symtable_del(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + zend_symtable_del(&pimple_obj->factories, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + zend_symtable_del(&pimple_obj->protected, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + zend_hash_index_del(&pimple_obj->values, index); + zend_hash_index_del(&pimple_obj->factories, index); + zend_hash_index_del(&pimple_obj->protected, index); + break; + default: + zend_error(E_WARNING, "Unsupported offset type"); + } +} + +static int pimple_object_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + pimple_bucket_value *retval = NULL; + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void **)&retval) == SUCCESS) { + switch (check_empty) { + case 0: /* isset */ + return 1; /* Differs from PHP behavior (Z_TYPE_P(retval->value) != IS_NULL;) */ + case 1: /* empty */ + default: + return zend_is_true(retval->value); + } + } + return 0; + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pimple_obj->values, index, (void **)&retval) == SUCCESS) { + switch (check_empty) { + case 0: /* isset */ + return 1; /* Differs from PHP behavior (Z_TYPE_P(retval->value) != IS_NULL;)*/ + case 1: /* empty */ + default: + return zend_is_true(retval->value); + } + } + return 0; + break; + default: + zend_error(E_WARNING, "Unsupported offset type"); + return 0; + } +} + +static zval *pimple_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + pimple_bucket_value *retval = NULL; + zend_fcall_info fci = {0}; + zval *retval_ptr_ptr = NULL; + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void **)&retval) == FAILURE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" is not defined.", Z_STRVAL_P(offset)); + return EG(uninitialized_zval_ptr); + } + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pimple_obj->values, index, (void **)&retval) == FAILURE) { + return EG(uninitialized_zval_ptr); + } + break; + case IS_NULL: /* $p[][3] = 'foo' first dim access */ + return EG(uninitialized_zval_ptr); + break; + default: + zend_error(E_WARNING, "Unsupported offset type"); + return EG(uninitialized_zval_ptr); + } + + if(retval->type == PIMPLE_IS_PARAM) { + return retval->value; + } + + if (zend_hash_index_exists(&pimple_obj->protected, retval->handle_num)) { + /* Service is protected, return the value every time */ + return retval->value; + } + + if (zend_hash_index_exists(&pimple_obj->factories, retval->handle_num)) { + /* Service is a factory, call it everytime and never cache its result */ + PIMPLE_CALL_CB + Z_DELREF_P(retval_ptr_ptr); /* fetch dim addr will increment refcount */ + return retval_ptr_ptr; + } + + if (retval->initialized == 1) { + /* Service has already been called, return its cached value */ + return retval->value; + } + + ALLOC_INIT_ZVAL(retval->raw); + MAKE_COPY_ZVAL(&retval->value, retval->raw); + + PIMPLE_CALL_CB + + retval->initialized = 1; + zval_ptr_dtor(&retval->value); + retval->value = retval_ptr_ptr; + + return retval->value; +} + +static int pimple_zval_is_valid_callback(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC) +{ + if (Z_TYPE_P(_zval) != IS_OBJECT) { + return FAILURE; + } + + if (_pimple_bucket_value->fcc.called_scope) { + return SUCCESS; + } + + if (Z_OBJ_HANDLER_P(_zval, get_closure) && Z_OBJ_HANDLER_P(_zval, get_closure)(_zval, &_pimple_bucket_value->fcc.calling_scope, &_pimple_bucket_value->fcc.function_handler, &_pimple_bucket_value->fcc.object_ptr TSRMLS_CC) == SUCCESS) { + _pimple_bucket_value->fcc.called_scope = _pimple_bucket_value->fcc.calling_scope; + return SUCCESS; + } else { + return FAILURE; + } +} + +static int pimple_zval_to_pimpleval(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC) +{ + _pimple_bucket_value->value = _zval; + + if (Z_TYPE_P(_zval) != IS_OBJECT) { + return PIMPLE_IS_PARAM; + } + + if (pimple_zval_is_valid_callback(_zval, _pimple_bucket_value TSRMLS_CC) == SUCCESS) { + _pimple_bucket_value->type = PIMPLE_IS_SERVICE; + _pimple_bucket_value->handle_num = Z_OBJ_HANDLE_P(_zval); + } + + return PIMPLE_IS_SERVICE; +} + +static void pimple_bucket_dtor(pimple_bucket_value *bucket) +{ + zval_ptr_dtor(&bucket->value); + pimple_free_bucket(bucket); +} + +PHP_METHOD(Pimple, protect) +{ + zval *protected = NULL; + pimple_object *pobj = NULL; + pimple_bucket_value bucket = {0}; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &protected) == FAILURE) { + return; + } + + if (pimple_zval_is_valid_callback(protected, &bucket TSRMLS_CC) == FAILURE) { + pimple_free_bucket(&bucket); + zend_throw_exception(spl_ce_InvalidArgumentException, "Callable is not a Closure or invokable object.", 0 TSRMLS_CC); + return; + } + + pimple_zval_to_pimpleval(protected, &bucket TSRMLS_CC); + pobj = (pimple_object *)zend_object_store_get_object(getThis() TSRMLS_CC); + + if (zend_hash_index_update(&pobj->protected, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL) == SUCCESS) { + Z_ADDREF_P(protected); + RETURN_ZVAL(protected, 1 , 0); + } else { + pimple_free_bucket(&bucket); + } + RETURN_FALSE; +} + +PHP_METHOD(Pimple, raw) +{ + zval *offset = NULL; + pimple_object *pobj = NULL; + pimple_bucket_value *value = NULL; + ulong index; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + pobj = zend_object_store_get_object(getThis() TSRMLS_CC); + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pobj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void *)&value) == FAILURE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" is not defined.", Z_STRVAL_P(offset)); + RETURN_NULL(); + } + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pobj->values, index, (void *)&value) == FAILURE) { + RETURN_NULL(); + } + break; + case IS_NULL: + default: + zend_error(E_WARNING, "Unsupported offset type"); + } + + if (value->raw) { + RETVAL_ZVAL(value->raw, 1, 0); + } else { + RETVAL_ZVAL(value->value, 1, 0); + } +} + +PHP_METHOD(Pimple, extend) +{ + zval *offset = NULL, *callable = NULL, *pimple_closure_obj = NULL; + pimple_bucket_value bucket = {0}, *value = NULL; + pimple_object *pobj = NULL; + pimple_closure_object *pcobj = NULL; + ulong index; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &offset, &callable) == FAILURE) { + return; + } + + pobj = zend_object_store_get_object(getThis() TSRMLS_CC); + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pobj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void *)&value) == FAILURE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" is not defined.", Z_STRVAL_P(offset)); + RETURN_NULL(); + } + if (value->type != PIMPLE_IS_SERVICE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" does not contain an object definition.", Z_STRVAL_P(offset)); + RETURN_NULL(); + } + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pobj->values, index, (void *)&value) == FAILURE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%ld\" is not defined.", index); + RETURN_NULL(); + } + if (value->type != PIMPLE_IS_SERVICE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%ld\" does not contain an object definition.", index); + RETURN_NULL(); + } + break; + case IS_NULL: + default: + zend_error(E_WARNING, "Unsupported offset type"); + } + + if (pimple_zval_is_valid_callback(callable, &bucket TSRMLS_CC) == FAILURE) { + pimple_free_bucket(&bucket); + zend_throw_exception(spl_ce_InvalidArgumentException, "Extension service definition is not a Closure or invokable object.", 0 TSRMLS_CC); + RETURN_NULL(); + } + pimple_free_bucket(&bucket); + + ALLOC_INIT_ZVAL(pimple_closure_obj); + object_init_ex(pimple_closure_obj, pimple_closure_ce); + + pcobj = zend_object_store_get_object(pimple_closure_obj TSRMLS_CC); + pcobj->callable = callable; + pcobj->factory = value->value; + Z_ADDREF_P(callable); + Z_ADDREF_P(value->value); + + if (zend_hash_index_exists(&pobj->factories, value->handle_num)) { + pimple_zval_to_pimpleval(pimple_closure_obj, &bucket TSRMLS_CC); + zend_hash_index_del(&pobj->factories, value->handle_num); + zend_hash_index_update(&pobj->factories, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL); + Z_ADDREF_P(pimple_closure_obj); + } + + pimple_object_write_dimension(getThis(), offset, pimple_closure_obj TSRMLS_CC); + + RETVAL_ZVAL(pimple_closure_obj, 1, 1); +} + +PHP_METHOD(Pimple, keys) +{ + HashPosition pos; + pimple_object *pobj = NULL; + zval **value = NULL; + zval *endval = NULL; + char *str_index = NULL; + int str_len; + ulong num_index; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + pobj = zend_object_store_get_object(getThis() TSRMLS_CC); + array_init_size(return_value, zend_hash_num_elements(&pobj->values)); + + zend_hash_internal_pointer_reset_ex(&pobj->values, &pos); + + while(zend_hash_get_current_data_ex(&pobj->values, (void **)&value, &pos) == SUCCESS) { + MAKE_STD_ZVAL(endval); + switch (zend_hash_get_current_key_ex(&pobj->values, &str_index, (uint *)&str_len, &num_index, 0, &pos)) { + case HASH_KEY_IS_STRING: + ZVAL_STRINGL(endval, str_index, str_len - 1, 1); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &endval, sizeof(zval *), NULL); + break; + case HASH_KEY_IS_LONG: + ZVAL_LONG(endval, num_index); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &endval, sizeof(zval *), NULL); + break; + } + zend_hash_move_forward_ex(&pobj->values, &pos); + } +} + +PHP_METHOD(Pimple, factory) +{ + zval *factory = NULL; + pimple_object *pobj = NULL; + pimple_bucket_value bucket = {0}; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &factory) == FAILURE) { + return; + } + + if (pimple_zval_is_valid_callback(factory, &bucket TSRMLS_CC) == FAILURE) { + pimple_free_bucket(&bucket); + zend_throw_exception(spl_ce_InvalidArgumentException, "Service definition is not a Closure or invokable object.", 0 TSRMLS_CC); + return; + } + + pimple_zval_to_pimpleval(factory, &bucket TSRMLS_CC); + pobj = (pimple_object *)zend_object_store_get_object(getThis() TSRMLS_CC); + + if (zend_hash_index_update(&pobj->factories, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL) == SUCCESS) { + Z_ADDREF_P(factory); + RETURN_ZVAL(factory, 1 , 0); + } else { + pimple_free_bucket(&bucket); + } + + RETURN_FALSE; +} + +PHP_METHOD(Pimple, offsetSet) +{ + zval *offset = NULL, *value = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &offset, &value) == FAILURE) { + return; + } + + pimple_object_write_dimension(getThis(), offset, value TSRMLS_CC); +} + +PHP_METHOD(Pimple, offsetGet) +{ + zval *offset = NULL, *retval = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + retval = pimple_object_read_dimension(getThis(), offset, 0 TSRMLS_CC); + + RETVAL_ZVAL(retval, 1, 0); +} + +PHP_METHOD(Pimple, offsetUnset) +{ + zval *offset = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + pimple_object_unset_dimension(getThis(), offset TSRMLS_CC); +} + +PHP_METHOD(Pimple, offsetExists) +{ + zval *offset = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + RETVAL_BOOL(pimple_object_has_dimension(getThis(), offset, 1 TSRMLS_CC)); +} + +PHP_METHOD(Pimple, register) +{ + zval *provider; + zval **data; + zval *retval = NULL; + zval key; + + HashTable *array = NULL; + HashPosition pos; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|h", &provider, pimple_serviceprovider_ce, &array) == FAILURE) { + return; + } + + RETVAL_ZVAL(getThis(), 1, 0); + + zend_call_method_with_1_params(&provider, Z_OBJCE_P(provider), NULL, "register", &retval, getThis()); + + if (retval) { + zval_ptr_dtor(&retval); + } + + if (!array) { + return; + } + + zend_hash_internal_pointer_reset_ex(array, &pos); + + while(zend_hash_get_current_data_ex(array, (void **)&data, &pos) == SUCCESS) { + zend_hash_get_current_key_zval_ex(array, &key, &pos); + pimple_object_write_dimension(getThis(), &key, *data TSRMLS_CC); + zend_hash_move_forward_ex(array, &pos); + } +} + +PHP_METHOD(Pimple, __construct) +{ + zval *values = NULL, **pData = NULL, offset; + HashPosition pos; + char *str_index = NULL; + zend_uint str_length; + ulong num_index; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a!", &values) == FAILURE || !values) { + return; + } + + zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(values), &pos); + while (zend_hash_has_more_elements_ex(Z_ARRVAL_P(values), &pos) == SUCCESS) { + zend_hash_get_current_data_ex(Z_ARRVAL_P(values), (void **)&pData, &pos); + zend_hash_get_current_key_ex(Z_ARRVAL_P(values), &str_index, &str_length, &num_index, 0, &pos); + INIT_ZVAL(offset); + if (zend_hash_get_current_key_type_ex(Z_ARRVAL_P(values), &pos) == HASH_KEY_IS_LONG) { + ZVAL_LONG(&offset, num_index); + } else { + ZVAL_STRINGL(&offset, str_index, (str_length - 1), 0); + } + pimple_object_write_dimension(getThis(), &offset, *pData TSRMLS_CC); + zend_hash_move_forward_ex(Z_ARRVAL_P(values), &pos); + } +} + +/* + * This is PHP code snippet handling extend()s calls : + + $extended = function ($c) use ($callable, $factory) { + return $callable($factory($c), $c); + }; + + */ +PHP_METHOD(PimpleClosure, invoker) +{ + pimple_closure_object *pcobj = NULL; + zval *arg = NULL, *retval = NULL, *newretval = NULL; + zend_fcall_info fci = {0}; + zval **args[2]; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arg) == FAILURE) { + return; + } + + pcobj = zend_object_store_get_object(getThis() TSRMLS_CC); + + fci.function_name = pcobj->factory; + args[0] = &arg; + zend_fcall_info_argp(&fci TSRMLS_CC, 1, args); + fci.retval_ptr_ptr = &retval; + fci.size = sizeof(fci); + + if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE || EG(exception)) { + efree(fci.params); + return; /* Should here return default zval */ + } + + efree(fci.params); + memset(&fci, 0, sizeof(fci)); + fci.size = sizeof(fci); + + fci.function_name = pcobj->callable; + args[0] = &retval; + args[1] = &arg; + zend_fcall_info_argp(&fci TSRMLS_CC, 2, args); + fci.retval_ptr_ptr = &newretval; + + if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE || EG(exception)) { + efree(fci.params); + zval_ptr_dtor(&retval); + return; + } + + efree(fci.params); + zval_ptr_dtor(&retval); + + RETVAL_ZVAL(newretval, 1 ,1); +} + +PHP_MINIT_FUNCTION(pimple) +{ + zend_class_entry tmp_pimple_ce, tmp_pimple_closure_ce, tmp_pimple_serviceprovider_iface_ce; + INIT_NS_CLASS_ENTRY(tmp_pimple_ce, PIMPLE_NS, "Container", pimple_ce_functions); + INIT_NS_CLASS_ENTRY(tmp_pimple_closure_ce, PIMPLE_NS, "ContainerClosure", NULL); + INIT_NS_CLASS_ENTRY(tmp_pimple_serviceprovider_iface_ce, PIMPLE_NS, "ServiceProviderInterface", pimple_serviceprovider_iface_ce_functions); + + tmp_pimple_ce.create_object = pimple_object_create; + tmp_pimple_closure_ce.create_object = pimple_closure_object_create; + + pimple_ce = zend_register_internal_class(&tmp_pimple_ce TSRMLS_CC); + zend_class_implements(pimple_ce TSRMLS_CC, 1, zend_ce_arrayaccess); + + pimple_closure_ce = zend_register_internal_class(&tmp_pimple_closure_ce TSRMLS_CC); + pimple_closure_ce->ce_flags |= ZEND_ACC_FINAL_CLASS; + + pimple_serviceprovider_ce = zend_register_internal_interface(&tmp_pimple_serviceprovider_iface_ce TSRMLS_CC); + + memcpy(&pimple_closure_object_handlers, zend_get_std_object_handlers(), sizeof(*zend_get_std_object_handlers())); + pimple_object_handlers = std_object_handlers; + pimple_closure_object_handlers.get_closure = pimple_closure_get_closure; + + pimple_closure_invoker_function.function_name = "Pimple closure internal invoker"; + pimple_closure_invoker_function.fn_flags |= ZEND_ACC_CLOSURE; + pimple_closure_invoker_function.handler = ZEND_MN(PimpleClosure_invoker); + pimple_closure_invoker_function.num_args = 1; + pimple_closure_invoker_function.required_num_args = 1; + pimple_closure_invoker_function.scope = pimple_closure_ce; + pimple_closure_invoker_function.type = ZEND_INTERNAL_FUNCTION; + pimple_closure_invoker_function.module = &pimple_module_entry; + + return SUCCESS; +} + +PHP_MINFO_FUNCTION(pimple) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "SensioLabs Pimple C support", "enabled"); + php_info_print_table_row(2, "Pimple supported version", PIMPLE_VERSION); + php_info_print_table_end(); + + php_info_print_box_start(0); + php_write((void *)ZEND_STRL("SensioLabs Pimple C support developed by Julien Pauli") TSRMLS_CC); + if (!sapi_module.phpinfo_as_text) { + php_write((void *)ZEND_STRL(sensiolabs_logo) TSRMLS_CC); + } + php_info_print_box_end(); +} + +zend_module_entry pimple_module_entry = { + STANDARD_MODULE_HEADER, + "pimple", + NULL, + PHP_MINIT(pimple), + NULL, + NULL, + NULL, + PHP_MINFO(pimple), + PIMPLE_VERSION, + STANDARD_MODULE_PROPERTIES +}; + +#ifdef COMPILE_DL_PIMPLE +ZEND_GET_MODULE(pimple) +#endif diff --git a/vendor/pimple/pimple/ext/pimple/pimple_compat.h b/vendor/pimple/pimple/ext/pimple/pimple_compat.h new file mode 100644 index 00000000..d234e174 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/pimple_compat.h @@ -0,0 +1,81 @@ + +/* + * This file is part of Pimple. + * + * Copyright (c) 2014 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. + */ + +#ifndef PIMPLE_COMPAT_H_ +#define PIMPLE_COMPAT_H_ + +#include "Zend/zend_extensions.h" /* for ZEND_EXTENSION_API_NO */ + +#define PHP_5_0_X_API_NO 220040412 +#define PHP_5_1_X_API_NO 220051025 +#define PHP_5_2_X_API_NO 220060519 +#define PHP_5_3_X_API_NO 220090626 +#define PHP_5_4_X_API_NO 220100525 +#define PHP_5_5_X_API_NO 220121212 +#define PHP_5_6_X_API_NO 220131226 + +#define IS_PHP_56 ZEND_EXTENSION_API_NO == PHP_5_6_X_API_NO +#define IS_AT_LEAST_PHP_56 ZEND_EXTENSION_API_NO >= PHP_5_6_X_API_NO + +#define IS_PHP_55 ZEND_EXTENSION_API_NO == PHP_5_5_X_API_NO +#define IS_AT_LEAST_PHP_55 ZEND_EXTENSION_API_NO >= PHP_5_5_X_API_NO + +#define IS_PHP_54 ZEND_EXTENSION_API_NO == PHP_5_4_X_API_NO +#define IS_AT_LEAST_PHP_54 ZEND_EXTENSION_API_NO >= PHP_5_4_X_API_NO + +#define IS_PHP_53 ZEND_EXTENSION_API_NO == PHP_5_3_X_API_NO +#define IS_AT_LEAST_PHP_53 ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO + +#if IS_PHP_53 +#define object_properties_init(obj, ce) do { \ + zend_hash_copy(obj->properties, &ce->default_properties, zval_copy_property_ctor(ce), NULL, sizeof(zval *)); \ + } while (0); +#endif + +#define ZEND_OBJ_INIT(obj, ce) do { \ + zend_object_std_init(obj, ce TSRMLS_CC); \ + object_properties_init((obj), (ce)); \ + } while(0); + +#if IS_PHP_53 || IS_PHP_54 +static void zend_hash_get_current_key_zval_ex(const HashTable *ht, zval *key, HashPosition *pos) { + Bucket *p; + + p = pos ? (*pos) : ht->pInternalPointer; + + if (!p) { + Z_TYPE_P(key) = IS_NULL; + } else if (p->nKeyLength) { + Z_TYPE_P(key) = IS_STRING; + Z_STRVAL_P(key) = estrndup(p->arKey, p->nKeyLength - 1); + Z_STRLEN_P(key) = p->nKeyLength - 1; + } else { + Z_TYPE_P(key) = IS_LONG; + Z_LVAL_P(key) = p->h; + } +} +#endif + +#endif /* PIMPLE_COMPAT_H_ */ diff --git a/vendor/pimple/pimple/ext/pimple/tests/001.phpt b/vendor/pimple/pimple/ext/pimple/tests/001.phpt new file mode 100644 index 00000000..0809ea23 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/001.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test for read_dim/write_dim handlers +--SKIPIF-- + +--FILE-- + + +--EXPECTF-- +foo +42 +foo2 +foo99 +baz +strstr \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/002.phpt b/vendor/pimple/pimple/ext/pimple/tests/002.phpt new file mode 100644 index 00000000..7b56d2c1 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/002.phpt @@ -0,0 +1,15 @@ +--TEST-- +Test for constructor +--SKIPIF-- + +--FILE-- +'foo')); +var_dump($p[42]); +?> +--EXPECT-- +NULL +string(3) "foo" diff --git a/vendor/pimple/pimple/ext/pimple/tests/003.phpt b/vendor/pimple/pimple/ext/pimple/tests/003.phpt new file mode 100644 index 00000000..a22cfa35 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/003.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test empty dimensions +--SKIPIF-- + +--FILE-- + +--EXPECT-- +int(42) +string(3) "bar" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/004.phpt b/vendor/pimple/pimple/ext/pimple/tests/004.phpt new file mode 100644 index 00000000..1e1d2513 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/004.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test has/unset dim handlers +--SKIPIF-- + +--FILE-- + +--EXPECT-- +int(42) +NULL +bool(true) +bool(false) +bool(true) +bool(true) \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/005.phpt b/vendor/pimple/pimple/ext/pimple/tests/005.phpt new file mode 100644 index 00000000..0479ee05 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/005.phpt @@ -0,0 +1,27 @@ +--TEST-- +Test simple class inheritance +--SKIPIF-- + +--FILE-- +someAttr; +?> +--EXPECT-- +string(3) "hit" +foo +fooAttr \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/006.phpt b/vendor/pimple/pimple/ext/pimple/tests/006.phpt new file mode 100644 index 00000000..cfe8a119 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/006.phpt @@ -0,0 +1,51 @@ +--TEST-- +Test complex class inheritance +--SKIPIF-- + +--FILE-- + 'bar', 88 => 'baz'); + +$p = new TestPimple($defaultValues); +$p[42] = 'foo'; +var_dump($p[42]); +var_dump($p[0]); +?> +--EXPECT-- +string(13) "hit offsetset" +string(27) "hit offsetget in TestPimple" +string(25) "hit offsetget in MyPimple" +string(3) "foo" +string(27) "hit offsetget in TestPimple" +string(25) "hit offsetget in MyPimple" +string(3) "baz" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/007.phpt b/vendor/pimple/pimple/ext/pimple/tests/007.phpt new file mode 100644 index 00000000..5aac6838 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/007.phpt @@ -0,0 +1,22 @@ +--TEST-- +Test for read_dim/write_dim handlers +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +foo +42 \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/008.phpt b/vendor/pimple/pimple/ext/pimple/tests/008.phpt new file mode 100644 index 00000000..db7eeec4 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/008.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test frozen services +--SKIPIF-- + +--FILE-- + +--EXPECTF-- diff --git a/vendor/pimple/pimple/ext/pimple/tests/009.phpt b/vendor/pimple/pimple/ext/pimple/tests/009.phpt new file mode 100644 index 00000000..bb05ea29 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/009.phpt @@ -0,0 +1,13 @@ +--TEST-- +Test service is called as callback, and only once +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/010.phpt b/vendor/pimple/pimple/ext/pimple/tests/010.phpt new file mode 100644 index 00000000..badce014 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/010.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test service is called as callback for every callback type +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +callme +called +Foo::bar +array(2) { + [0]=> + string(3) "Foo" + [1]=> + string(3) "bar" +} \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/011.phpt b/vendor/pimple/pimple/ext/pimple/tests/011.phpt new file mode 100644 index 00000000..6682ab8e --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/011.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test service callback throwing an exception +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +all right! \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/012.phpt b/vendor/pimple/pimple/ext/pimple/tests/012.phpt new file mode 100644 index 00000000..4c6ac486 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/012.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test service factory +--SKIPIF-- + +--FILE-- +factory($f = function() { var_dump('called-1'); return 'ret-1';}); + +$p[] = $f; + +$p[] = function () { var_dump('called-2'); return 'ret-2'; }; + +var_dump($p[0]); +var_dump($p[0]); +var_dump($p[1]); +var_dump($p[1]); +?> +--EXPECTF-- +string(8) "called-1" +string(5) "ret-1" +string(8) "called-1" +string(5) "ret-1" +string(8) "called-2" +string(5) "ret-2" +string(5) "ret-2" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/013.phpt b/vendor/pimple/pimple/ext/pimple/tests/013.phpt new file mode 100644 index 00000000..f419958c --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/013.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test keys() +--SKIPIF-- + +--FILE-- +keys()); + +$p['foo'] = 'bar'; +$p[] = 'foo'; + +var_dump($p->keys()); + +unset($p['foo']); + +var_dump($p->keys()); +?> +--EXPECTF-- +array(0) { +} +array(2) { + [0]=> + string(3) "foo" + [1]=> + int(0) +} +array(1) { + [0]=> + int(0) +} \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/014.phpt b/vendor/pimple/pimple/ext/pimple/tests/014.phpt new file mode 100644 index 00000000..ac937213 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/014.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test raw() +--SKIPIF-- + +--FILE-- +raw('foo')); +var_dump($p[42]); + +unset($p['foo']); + +try { + $p->raw('foo'); + echo "expected exception"; +} catch (InvalidArgumentException $e) { } +--EXPECTF-- +string(8) "called-2" +string(5) "ret-2" +object(Closure)#%i (0) { +} +string(8) "called-2" +string(5) "ret-2" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/015.phpt b/vendor/pimple/pimple/ext/pimple/tests/015.phpt new file mode 100644 index 00000000..314f008a --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/015.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test protect() +--SKIPIF-- + +--FILE-- +protect($f); + +var_dump($p['foo']); +--EXPECTF-- +object(Closure)#%i (0) { +} \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/016.phpt b/vendor/pimple/pimple/ext/pimple/tests/016.phpt new file mode 100644 index 00000000..e55edb0a --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/016.phpt @@ -0,0 +1,24 @@ +--TEST-- +Test extend() +--SKIPIF-- + +--FILE-- +extend(12, function ($w) { var_dump($w); return 'bar'; }); /* $callable in code above */ + +var_dump($c('param')); +--EXPECTF-- +string(5) "param" +string(3) "foo" +string(3) "bar" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/017.phpt b/vendor/pimple/pimple/ext/pimple/tests/017.phpt new file mode 100644 index 00000000..bac23ce0 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/017.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test extend() with exception in service extension +--SKIPIF-- + +--FILE-- +extend(12, function ($w) { throw new BadMethodCallException; }); + +try { + $p[12]; + echo "Exception expected"; +} catch (BadMethodCallException $e) { } +--EXPECTF-- diff --git a/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt b/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt new file mode 100644 index 00000000..8f881d6e --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test extend() with exception in service factory +--SKIPIF-- + +--FILE-- +extend(12, function ($w) { return 'foobar'; }); + +try { + $p[12]; + echo "Exception expected"; +} catch (BadMethodCallException $e) { } +--EXPECTF-- diff --git a/vendor/pimple/pimple/ext/pimple/tests/018.phpt b/vendor/pimple/pimple/ext/pimple/tests/018.phpt new file mode 100644 index 00000000..27c12a14 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/018.phpt @@ -0,0 +1,23 @@ +--TEST-- +Test register() +--SKIPIF-- + +--FILE-- +register(new Foo, array(42 => 'bar')); + +var_dump($p[42]); +--EXPECTF-- +object(Pimple\Container)#1 (0) { +} +string(3) "bar" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/019.phpt b/vendor/pimple/pimple/ext/pimple/tests/019.phpt new file mode 100644 index 00000000..28a9aeca --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/019.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test register() returns static and is a fluent interface +--SKIPIF-- + +--FILE-- +register(new Foo)); +--EXPECTF-- +bool(true) diff --git a/vendor/pimple/pimple/ext/pimple/tests/bench.phpb b/vendor/pimple/pimple/ext/pimple/tests/bench.phpb new file mode 100644 index 00000000..8f983e65 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/bench.phpb @@ -0,0 +1,51 @@ +factory($factory); + +$p['factory'] = $factory; + +echo $p['factory']; +echo $p['factory']; +echo $p['factory']; + +} + +echo microtime(true) - $time; diff --git a/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb b/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb new file mode 100644 index 00000000..aec541f0 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb @@ -0,0 +1,25 @@ + diff --git a/vendor/pimple/pimple/phpunit.xml.dist b/vendor/pimple/pimple/phpunit.xml.dist new file mode 100644 index 00000000..5c8d487f --- /dev/null +++ b/vendor/pimple/pimple/phpunit.xml.dist @@ -0,0 +1,14 @@ + + + + + + ./src/Pimple/Tests + + + diff --git a/vendor/pimple/pimple/src/Pimple/Container.php b/vendor/pimple/pimple/src/Pimple/Container.php new file mode 100644 index 00000000..c976431e --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Container.php @@ -0,0 +1,282 @@ +factories = new \SplObjectStorage(); + $this->protected = new \SplObjectStorage(); + + foreach ($values as $key => $value) { + $this->offsetSet($key, $value); + } + } + + /** + * Sets a parameter or an object. + * + * Objects must be defined as Closures. + * + * Allowing any PHP callable leads to difficult to debug problems + * as function names (strings) are callable (creating a function with + * the same name as an existing parameter would break your container). + * + * @param string $id The unique identifier for the parameter or object + * @param mixed $value The value of the parameter or a closure to define an object + * + * @throws \RuntimeException Prevent override of a frozen service + */ + public function offsetSet($id, $value) + { + if (isset($this->frozen[$id])) { + throw new \RuntimeException(sprintf('Cannot override frozen service "%s".', $id)); + } + + $this->values[$id] = $value; + $this->keys[$id] = true; + } + + /** + * Gets a parameter or an object. + * + * @param string $id The unique identifier for the parameter or object + * + * @return mixed The value of the parameter or an object + * + * @throws \InvalidArgumentException if the identifier is not defined + */ + public function offsetGet($id) + { + if (!isset($this->keys[$id])) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); + } + + if ( + isset($this->raw[$id]) + || !is_object($this->values[$id]) + || isset($this->protected[$this->values[$id]]) + || !method_exists($this->values[$id], '__invoke') + ) { + return $this->values[$id]; + } + + if (isset($this->factories[$this->values[$id]])) { + return $this->values[$id]($this); + } + + $raw = $this->values[$id]; + $val = $this->values[$id] = $raw($this); + $this->raw[$id] = $raw; + + $this->frozen[$id] = true; + + return $val; + } + + /** + * Checks if a parameter or an object is set. + * + * @param string $id The unique identifier for the parameter or object + * + * @return bool + */ + public function offsetExists($id) + { + return isset($this->keys[$id]); + } + + /** + * Unsets a parameter or an object. + * + * @param string $id The unique identifier for the parameter or object + */ + public function offsetUnset($id) + { + if (isset($this->keys[$id])) { + if (is_object($this->values[$id])) { + unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]); + } + + unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]); + } + } + + /** + * Marks a callable as being a factory service. + * + * @param callable $callable A service definition to be used as a factory + * + * @return callable The passed callable + * + * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object + */ + public function factory($callable) + { + if (!method_exists($callable, '__invoke')) { + throw new \InvalidArgumentException('Service definition is not a Closure or invokable object.'); + } + + $this->factories->attach($callable); + + return $callable; + } + + /** + * Protects a callable from being interpreted as a service. + * + * This is useful when you want to store a callable as a parameter. + * + * @param callable $callable A callable to protect from being evaluated + * + * @return callable The passed callable + * + * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object + */ + public function protect($callable) + { + if (!method_exists($callable, '__invoke')) { + throw new \InvalidArgumentException('Callable is not a Closure or invokable object.'); + } + + $this->protected->attach($callable); + + return $callable; + } + + /** + * Gets a parameter or the closure defining an object. + * + * @param string $id The unique identifier for the parameter or object + * + * @return mixed The value of the parameter or the closure defining an object + * + * @throws \InvalidArgumentException if the identifier is not defined + */ + public function raw($id) + { + if (!isset($this->keys[$id])) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); + } + + if (isset($this->raw[$id])) { + return $this->raw[$id]; + } + + return $this->values[$id]; + } + + /** + * Extends an object definition. + * + * Useful when you want to extend an existing object definition, + * without necessarily loading that object. + * + * @param string $id The unique identifier for the object + * @param callable $callable A service definition to extend the original + * + * @return callable The wrapped callable + * + * @throws \InvalidArgumentException if the identifier is not defined or not a service definition + */ + public function extend($id, $callable) + { + if (!isset($this->keys[$id])) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); + } + + if (!is_object($this->values[$id]) || !method_exists($this->values[$id], '__invoke')) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" does not contain an object definition.', $id)); + } + + if (!is_object($callable) || !method_exists($callable, '__invoke')) { + throw new \InvalidArgumentException('Extension service definition is not a Closure or invokable object.'); + } + + $factory = $this->values[$id]; + + $extended = function ($c) use ($callable, $factory) { + return $callable($factory($c), $c); + }; + + if (isset($this->factories[$factory])) { + $this->factories->detach($factory); + $this->factories->attach($extended); + } + + return $this[$id] = $extended; + } + + /** + * Returns all defined value names. + * + * @return array An array of value names + */ + public function keys() + { + return array_keys($this->values); + } + + /** + * Registers a service provider. + * + * @param ServiceProviderInterface $provider A ServiceProviderInterface instance + * @param array $values An array of values that customizes the provider + * + * @return static + */ + public function register(ServiceProviderInterface $provider, array $values = array()) + { + $provider->register($this); + + foreach ($values as $key => $value) { + $this[$key] = $value; + } + + return $this; + } +} diff --git a/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php b/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php new file mode 100644 index 00000000..c004594b --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php @@ -0,0 +1,46 @@ +value = $value; + + return $service; + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php new file mode 100644 index 00000000..33cd4e54 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php @@ -0,0 +1,34 @@ +factory(function () { + return new Service(); + }); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php new file mode 100644 index 00000000..d71b184d --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php @@ -0,0 +1,35 @@ + + */ +class Service +{ + public $value; +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php b/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php new file mode 100644 index 00000000..8e5c4c73 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php @@ -0,0 +1,76 @@ + + */ +class PimpleServiceProviderInterfaceTest extends \PHPUnit_Framework_TestCase +{ + public function testProvider() + { + $pimple = new Container(); + + $pimpleServiceProvider = new Fixtures\PimpleServiceProvider(); + $pimpleServiceProvider->register($pimple); + + $this->assertEquals('value', $pimple['param']); + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); + + $serviceOne = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } + + public function testProviderWithRegisterMethod() + { + $pimple = new Container(); + + $pimple->register(new Fixtures\PimpleServiceProvider(), array( + 'anotherParameter' => 'anotherValue', + )); + + $this->assertEquals('value', $pimple['param']); + $this->assertEquals('anotherValue', $pimple['anotherParameter']); + + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); + + $serviceOne = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php b/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php new file mode 100644 index 00000000..918f620d --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php @@ -0,0 +1,440 @@ + + */ +class PimpleTest extends \PHPUnit_Framework_TestCase +{ + public function testWithString() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + + $this->assertEquals('value', $pimple['param']); + } + + public function testWithClosure() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); + } + + public function testServicesShouldBeDifferent() + { + $pimple = new Container(); + $pimple['service'] = $pimple->factory(function () { + return new Fixtures\Service(); + }); + + $serviceOne = $pimple['service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } + + public function testShouldPassContainerAsParameter() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $pimple['container'] = function ($container) { + return $container; + }; + + $this->assertNotSame($pimple, $pimple['service']); + $this->assertSame($pimple, $pimple['container']); + } + + public function testIsset() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + $pimple['null'] = null; + + $this->assertTrue(isset($pimple['param'])); + $this->assertTrue(isset($pimple['service'])); + $this->assertTrue(isset($pimple['null'])); + $this->assertFalse(isset($pimple['non_existent'])); + } + + public function testConstructorInjection() + { + $params = array('param' => 'value'); + $pimple = new Container($params); + + $this->assertSame($params['param'], $pimple['param']); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testOffsetGetValidatesKeyIsPresent() + { + $pimple = new Container(); + echo $pimple['foo']; + } + + public function testOffsetGetHonorsNullValues() + { + $pimple = new Container(); + $pimple['foo'] = null; + $this->assertNull($pimple['foo']); + } + + public function testUnset() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + unset($pimple['param'], $pimple['service']); + $this->assertFalse(isset($pimple['param'])); + $this->assertFalse(isset($pimple['service'])); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testShare($service) + { + $pimple = new Container(); + $pimple['shared_service'] = $service; + + $serviceOne = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertSame($serviceOne, $serviceTwo); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testProtect($service) + { + $pimple = new Container(); + $pimple['protected'] = $pimple->protect($service); + + $this->assertSame($service, $pimple['protected']); + } + + public function testGlobalFunctionNameAsParameterValue() + { + $pimple = new Container(); + $pimple['global_function'] = 'strlen'; + $this->assertSame('strlen', $pimple['global_function']); + } + + public function testRaw() + { + $pimple = new Container(); + $pimple['service'] = $definition = $pimple->factory(function () { return 'foo'; }); + $this->assertSame($definition, $pimple->raw('service')); + } + + public function testRawHonorsNullValues() + { + $pimple = new Container(); + $pimple['foo'] = null; + $this->assertNull($pimple->raw('foo')); + } + + public function testFluentRegister() + { + $pimple = new Container(); + $this->assertSame($pimple, $pimple->register($this->getMock('Pimple\ServiceProviderInterface'))); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testRawValidatesKeyIsPresent() + { + $pimple = new Container(); + $pimple->raw('foo'); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testExtend($service) + { + $pimple = new Container(); + $pimple['shared_service'] = function () { + return new Fixtures\Service(); + }; + $pimple['factory_service'] = $pimple->factory(function () { + return new Fixtures\Service(); + }); + + $pimple->extend('shared_service', $service); + $serviceOne = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + $serviceTwo = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + $this->assertSame($serviceOne, $serviceTwo); + $this->assertSame($serviceOne->value, $serviceTwo->value); + + $pimple->extend('factory_service', $service); + $serviceOne = $pimple['factory_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + $serviceTwo = $pimple['factory_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + $this->assertNotSame($serviceOne, $serviceTwo); + $this->assertNotSame($serviceOne->value, $serviceTwo->value); + } + + public function testExtendDoesNotLeakWithFactories() + { + if (extension_loaded('pimple')) { + $this->markTestSkipped('Pimple extension does not support this test'); + } + $pimple = new Container(); + + $pimple['foo'] = $pimple->factory(function () { return; }); + $pimple['foo'] = $pimple->extend('foo', function ($foo, $pimple) { return; }); + unset($pimple['foo']); + + $p = new \ReflectionProperty($pimple, 'values'); + $p->setAccessible(true); + $this->assertEmpty($p->getValue($pimple)); + + $p = new \ReflectionProperty($pimple, 'factories'); + $p->setAccessible(true); + $this->assertCount(0, $p->getValue($pimple)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testExtendValidatesKeyIsPresent() + { + $pimple = new Container(); + $pimple->extend('foo', function () {}); + } + + public function testKeys() + { + $pimple = new Container(); + $pimple['foo'] = 123; + $pimple['bar'] = 123; + + $this->assertEquals(array('foo', 'bar'), $pimple->keys()); + } + + /** @test */ + public function settingAnInvokableObjectShouldTreatItAsFactory() + { + $pimple = new Container(); + $pimple['invokable'] = new Fixtures\Invokable(); + + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['invokable']); + } + + /** @test */ + public function settingNonInvokableObjectShouldTreatItAsParameter() + { + $pimple = new Container(); + $pimple['non_invokable'] = new Fixtures\NonInvokable(); + + $this->assertInstanceOf('Pimple\Tests\Fixtures\NonInvokable', $pimple['non_invokable']); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Service definition is not a Closure or invokable object. + */ + public function testFactoryFailsForInvalidServiceDefinitions($service) + { + $pimple = new Container(); + $pimple->factory($service); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Callable is not a Closure or invokable object. + */ + public function testProtectFailsForInvalidServiceDefinitions($service) + { + $pimple = new Container(); + $pimple->protect($service); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" does not contain an object definition. + */ + public function testExtendFailsForKeysNotContainingServiceDefinitions($service) + { + $pimple = new Container(); + $pimple['foo'] = $service; + $pimple->extend('foo', function () {}); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Extension service definition is not a Closure or invokable object. + */ + public function testExtendFailsForInvalidServiceDefinitions($service) + { + $pimple = new Container(); + $pimple['foo'] = function () {}; + $pimple->extend('foo', $service); + } + + /** + * Provider for invalid service definitions. + */ + public function badServiceDefinitionProvider() + { + return array( + array(123), + array(new Fixtures\NonInvokable()), + ); + } + + /** + * Provider for service definitions. + */ + public function serviceDefinitionProvider() + { + return array( + array(function ($value) { + $service = new Fixtures\Service(); + $service->value = $value; + + return $service; + }), + array(new Fixtures\Invokable()), + ); + } + + public function testDefiningNewServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + $pimple['bar'] = function () { + return 'bar'; + }; + $this->assertSame('bar', $pimple['bar']); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Cannot override frozen service "foo". + */ + public function testOverridingServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + $pimple['foo'] = function () { + return 'bar'; + }; + } + + public function testRemovingServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + unset($pimple['foo']); + $pimple['foo'] = function () { + return 'bar'; + }; + $this->assertSame('bar', $pimple['foo']); + } + + public function testExtendingService() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) { + return "$foo.bar"; + }); + $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) { + return "$foo.baz"; + }); + $this->assertSame('foo.bar.baz', $pimple['foo']); + } + + public function testExtendingServiceAfterOtherServiceFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $pimple['bar'] = function () { + return 'bar'; + }; + $foo = $pimple['foo']; + + $pimple['bar'] = $pimple->extend('bar', function ($bar, $app) { + return "$bar.baz"; + }); + $this->assertSame('bar.baz', $pimple['bar']); + } +} diff --git a/vendor/psr/log/.gitignore b/vendor/psr/log/.gitignore new file mode 100644 index 00000000..22d0d82f --- /dev/null +++ b/vendor/psr/log/.gitignore @@ -0,0 +1 @@ +vendor diff --git a/vendor/psr/log/LICENSE b/vendor/psr/log/LICENSE new file mode 100644 index 00000000..474c952b --- /dev/null +++ b/vendor/psr/log/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 PHP Framework Interoperability Group + +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/vendor/psr/log/Psr/Log/AbstractLogger.php b/vendor/psr/log/Psr/Log/AbstractLogger.php new file mode 100644 index 00000000..90e721af --- /dev/null +++ b/vendor/psr/log/Psr/Log/AbstractLogger.php @@ -0,0 +1,128 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 00000000..67f852d1 --- /dev/null +++ b/vendor/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/vendor/psr/log/Psr/Log/LoggerInterface.php b/vendor/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 00000000..5ea72438 --- /dev/null +++ b/vendor/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,123 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/vendor/psr/log/Psr/Log/NullLogger.php b/vendor/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 00000000..d8cd682c --- /dev/null +++ b/vendor/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,28 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php new file mode 100644 index 00000000..a0391a52 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php @@ -0,0 +1,140 @@ + ". + * + * Example ->error('Foo') would yield "error Foo". + * + * @return string[] + */ + abstract public function getLogs(); + + public function testImplements() + { + $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + $level.' message of level '.$level.' with context: Bob', + $level.' message of level '.$level.' with context: Bob', + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + /** + * @expectedException \Psr\Log\InvalidArgumentException + */ + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + if (method_exists($this, 'createPartialMock')) { + $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); + } else { + $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); + } + $dummy->expects($this->once()) + ->method('__toString') + ->will($this->returnValue('DUMMY')); + + $this->getLogger()->warning($dummy); + + $expected = array('warning DUMMY'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextCanContainAnything() + { + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => new DummyTest), + 'object' => new \DateTime, + 'resource' => fopen('php://memory', 'r'), + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!' + ); + $this->assertEquals($expected, $this->getLogs()); + } +} + +class DummyTest +{ + public function __toString() + { + } +} diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md new file mode 100644 index 00000000..574bc1cb --- /dev/null +++ b/vendor/psr/log/README.md @@ -0,0 +1,45 @@ +PSR Log +======= + +This repository holds all interfaces/classes/traits related to +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). + +Note that this is not a logger of its own. It is merely an interface that +describes a logger. See the specification for more details. + +Usage +----- + +If you need a logger, you can use the interface like this: + +```php +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + // do something useful + } +} +``` + +You can then pick one of the implementations of the interface to get a logger. + +If you want to implement the interface, you can require this package and +implement `Psr\Log\LoggerInterface` in your code. Please read the +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +for details. diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json new file mode 100644 index 00000000..87934d70 --- /dev/null +++ b/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/silex/silex/.gitignore b/vendor/silex/silex/.gitignore new file mode 100644 index 00000000..3d4ff050 --- /dev/null +++ b/vendor/silex/silex/.gitignore @@ -0,0 +1,5 @@ +/phpunit.xml +/vendor +/build +/composer.lock + diff --git a/vendor/silex/silex/.travis.yml b/vendor/silex/silex/.travis.yml new file mode 100644 index 00000000..fed52268 --- /dev/null +++ b/vendor/silex/silex/.travis.yml @@ -0,0 +1,44 @@ +language: php + +sudo: false + +env: + global: + - SYMFONY_DEPRECATIONS_HELPER=weak + +cache: + directories: + - $HOME/.composer/cache/files + +before_install: + - if [[ $TRAVIS_PHP_VERSION != hhvm ]]; then phpenv config-rm xdebug.ini; fi + +before_script: + # symfony/* + - sh -c "if [ '$TWIG_VERSION' != '2.0' ]; then sed -i 's/~1.8|~2.0/~1.8/g' composer.json; composer update; fi" + - sh -c "if [ '$SYMFONY_DEPS_VERSION' = '3.0' ]; then sed -i 's/~2\.8|^3\.0/3.0.*@dev/g' composer.json; composer update; fi" + - sh -c "if [ '$SYMFONY_DEPS_VERSION' = '3.1' ]; then sed -i 's/~2\.8|^3\.0/3.1.*@dev/g' composer.json; composer update; fi" + - sh -c "if [ '$SYMFONY_DEPS_VERSION' = '3.2' ]; then sed -i 's/~2\.8|^3\.0/3.2.*@dev/g' composer.json; composer update; fi" + - sh -c "if [ '$SYMFONY_DEPS_VERSION' = '' ]; then sed -i 's/~2\.8|^3\.0/2.8.*@dev/g' composer.json; composer update; fi" + - composer install + +script: ./vendor/bin/simple-phpunit + +matrix: + include: + - php: 5.5 + - php: 5.6 + env: TWIG_VERSION=2.0 + - php: 5.6 + env: SYMFONY_DEPS_VERSION=3.0 + - php: 5.6 + env: SYMFONY_DEPS_VERSION=3.1 + - php: 5.6 + env: SYMFONY_DEPS_VERSION=3.2 + - php: 7.0 + - php: 7.1 + - php: hhvm + +cache: + directories: + - .phpunit diff --git a/vendor/silex/silex/LICENSE b/vendor/silex/silex/LICENSE new file mode 100644 index 00000000..b420d719 --- /dev/null +++ b/vendor/silex/silex/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-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/vendor/silex/silex/README.rst b/vendor/silex/silex/README.rst new file mode 100644 index 00000000..b79e47b6 --- /dev/null +++ b/vendor/silex/silex/README.rst @@ -0,0 +1,64 @@ +Silex, a simple Web Framework +============================= + +Silex is a PHP micro-framework to develop websites based on `Symfony +components`_: + +.. code-block:: php + + get('/hello/{name}', function ($name) use ($app) { + return 'Hello '.$app->escape($name); + }); + + $app->run(); + +Silex works with PHP 5.5.9 or later. + +Installation +------------ + +The recommended way to install Silex is through `Composer`_: + +.. code-block:: bash + + composer require silex/silex "~2.0" + +Alternatively, you can download the `silex.zip`_ file and extract it. + +More Information +---------------- + +Read the `documentation`_ for more information and `changelog +`_ for upgrading information. + +Tests +----- + +To run the test suite, you need `Composer`_ and `PHPUnit`_: + +.. code-block:: bash + + composer install + phpunit + +Community +--------- + +Check out #silex-php on irc.freenode.net. + +License +------- + +Silex is licensed under the MIT license. + +.. _Symfony components: http://symfony.com +.. _Composer: http://getcomposer.org +.. _PHPUnit: https://phpunit.de +.. _silex.zip: http://silex.sensiolabs.org/download +.. _documentation: http://silex.sensiolabs.org/documentation diff --git a/vendor/silex/silex/composer.json b/vendor/silex/silex/composer.json new file mode 100644 index 00000000..aa2f24ee --- /dev/null +++ b/vendor/silex/silex/composer.json @@ -0,0 +1,69 @@ +{ + "name": "silex/silex", + "description": "The PHP micro-framework based on the Symfony Components", + "keywords": ["microframework"], + "homepage": "http://silex.sensiolabs.org", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0", + "symfony/event-dispatcher": "~2.8|^3.0", + "symfony/http-foundation": "~2.8|^3.0", + "symfony/http-kernel": "~2.8|^3.0", + "symfony/routing": "~2.8|^3.0" + }, + "require-dev": { + "symfony/asset": "~2.8|^3.0", + "symfony/expression-language": "~2.8|^3.0", + "symfony/security": "~2.8|^3.0", + "symfony/config": "~2.8|^3.0", + "symfony/form": "~2.8|^3.0", + "symfony/browser-kit": "~2.8|^3.0", + "symfony/css-selector": "~2.8|^3.0", + "symfony/debug": "~2.8|^3.0", + "symfony/dom-crawler": "~2.8|^3.0", + "symfony/finder": "~2.8|^3.0", + "symfony/intl": "~2.8|^3.0", + "symfony/monolog-bridge": "~2.8|^3.0", + "symfony/doctrine-bridge": "~2.8|^3.0", + "symfony/options-resolver": "~2.8|^3.0", + "symfony/phpunit-bridge": "^3.2", + "symfony/process": "~2.8|^3.0", + "symfony/serializer": "~2.8|^3.0", + "symfony/translation": "~2.8|^3.0", + "symfony/twig-bridge": "~2.8|^3.0", + "symfony/validator": "~2.8|^3.0", + "symfony/var-dumper": "~2.8|^3.0", + "twig/twig": "~1.28|~2.0", + "doctrine/dbal": "~2.2", + "swiftmailer/swiftmailer": "~5", + "monolog/monolog": "^1.4.1", + "symfony/web-link": "^3.3" + }, + "replace": { + "silex/api": "self.version", + "silex/providers": "self.version" + }, + "autoload": { + "psr-4": { "Silex\\": "src/Silex" } + }, + "autoload-dev" : { + "psr-4": { "Silex\\Tests\\" : "tests/Silex/Tests" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/silex/silex/doc/changelog.rst b/vendor/silex/silex/doc/changelog.rst new file mode 100644 index 00000000..81beee92 --- /dev/null +++ b/vendor/silex/silex/doc/changelog.rst @@ -0,0 +1,378 @@ +Changelog +========= + +2.1.0 (2017-05-03) +------------------ + +* added more options to security.firewalls +* added WebLink component integration +* added parameters to configure the Twig core extension behavior +* fixed deprecation notices with symfony/twig-bridge 3.2+ in TwigServiceProvider +* added FormRegistry as a service to enable the extension point +* removed the build scripts +* fixed some deprecation warnings +* added support for registering Swiftmailer plugins + +2.0.4 (2016-11-06) +------------------ + +* fixed twig.app_variable definition +* added support for latest versions of Twig 1.x and 2.0 (Twig runtime loaders) +* added support for Symfony 2.3 + +2.0.3 (2016-08-22) +------------------ + +* fixed lazy evaluation of 'monolog.use_error_handler' +* fixed PHP7 type hint on controllers + +2.0.2 (2016-06-14) +------------------ + +* fixed Symfony 3.1 deprecations + +2.0.1 (2016-05-27) +------------------ + +* fixed the silex form extension registration to allow overriding default ones +* removed support for the obsolete Locale Symfony component (uses the Intl one now) +* added support for Symfony 3.1 + +2.0.0 (2016-05-18) +------------------ + +* decoupled the exception handler from HttpKernelServiceProvider +* Switched to BCrypt as the default encoder in the security provider +* added full support for RequestMatcher +* added support for Symfony Guard +* added support for callables in CallbackResolver +* added FormTrait::namedForm() +* added support for delivery_addresses, delivery_whitelist, and sender_address +* added support to register form types / form types extensions / form types guessers as services +* added support for callable in mounts (allow nested route collection to be built easily) +* added support for conditions on routes +* added support for the Symfony VarDumper Component +* added a global Twig variable (an AppVariable instance) +* [BC BREAK] CSRF has been moved to a standalone provider (``form.secret`` is not available anymore) +* added support for the Symfony HttpFoundation Twig bridge extension +* added support for the Symfony Asset Component +* bumped minimum version of Symfony to 2.8 +* bumped minimum version of PHP to 5.5.0 +* Updated Pimple to 3.0 +* Updated session listeners to extends HttpKernel ones +* [BC BREAK] Locale management has been moved to LocaleServiceProvider which must be registered + if you want Silex to manage your locale (must also be registered for the translation service provider) +* [BC BREAK] Provider interfaces moved to Silex\Api namespace, published as + separate package via subtree split +* [BC BREAK] ServiceProviderInterface split in to EventListenerProviderInterface + and BootableProviderInterface +* [BC BREAK] Service Provider support files moved under Silex\Provider + namespace, allowing publishing as separate package via sub-tree split +* ``monolog.exception.logger_filter`` option added to Monolog service provider +* [BC BREAK] ``$app['request']`` service removed, use ``$app['request_stack']`` instead + +1.3.6 (2016-XX-XX) +------------------ + +* n/a + +1.3.5 (2016-01-06) +------------------ + +* fixed typo in SecurityServiceProvider + +1.3.4 (2015-09-15) +------------------ + +* fixed some new deprecations +* fixed translation registration for the validators + +1.3.3 (2015-09-08) +------------------ + +* added support for Symfony 3.0 and Twig 2.0 +* fixed some Form deprecations +* removed deprecated method call in the exception handler +* fixed Swiftmailer spool flushing when spool is not enabled + +1.3.2 (2015-08-24) +------------------ + +* no changes + +1.3.1 (2015-08-04) +------------------ + +* added missing support for the Expression constraint +* fixed the possibility to override translations for validator error messages +* fixed sub-mounts with same name clash +* fixed session logout handler when a firewall is stateless + +1.3.0 (2015-06-05) +------------------ + +* added a `$app['user']` to get the current user (security provider) +* added view handlers +* added support for the OPTIONS HTTP method +* added caching for the Translator provider +* deprecated `$app['exception_handler']->disable()` in favor of `unset($app['exception_handler'])` +* made Silex compatible with Symfony 2.7 an 2.8 (and keep compatibility with Symfony 2.3, 2.5, and 2.6) +* removed deprecated TwigCoreExtension class (register the new HttpFragmentServiceProvider instead) +* bumped minimum version of PHP to 5.3.9 + +1.2.5 (2015-06-04) +------------------ + +* no code changes (last version of the 1.2 branch) + +1.2.4 (2015-04-11) +------------------ + +* fixed the exception message when mounting a collection that doesn't return a ControllerCollection +* fixed Symfony dependencies (Silex 1.2 is not compatible with Symfony 2.7) + +1.2.3 (2015-01-20) +------------------ + +* fixed remember me listener +* fixed translation files loading when they do not exist +* allowed global after middlewares to return responses like route specific ones + +1.2.2 (2014-09-26) +------------------ + +* fixed Translator locale management +* added support for the $app argument in application middlewares (to make it consistent with route middlewares) +* added form.types to the Form provider + +1.2.1 (2014-07-01) +------------------ + +* added support permissions in the Monolog provider +* fixed Switfmailer spool where the event dispatcher is different from the other ones +* fixed locale when changing it on the translator itself + +1.2.0 (2014-03-29) +------------------ + +* Allowed disabling the boot logic of MonologServiceProvider +* Reverted "convert attributes on the request that actually exist" +* [BC BREAK] Routes are now always added in the order of their registration (even for mounted routes) +* Added run() on Route to be able to define the controller code +* Deprecated TwigCoreExtension (register the new HttpFragmentServiceProvider instead) +* Added HttpFragmentServiceProvider +* Allowed a callback to be a method call on a service (before, after, finish, error, on Application; convert, before, after on Controller) + +1.1.3 (2013-XX-XX) +------------------ + +* Fixed translator locale management + +1.1.2 (2013-10-30) +------------------ + +* Added missing "security.hide_user_not_found" support in SecurityServiceProvider +* Fixed event listeners that are registered after the boot via the on() method + +1.0.2 (2013-10-30) +------------------ + +* Fixed SecurityServiceProvider to use null as a fake controller so that routes can be dumped + +1.1.1 (2013-10-11) +------------------ + +* Removed or replaced deprecated Symfony code +* Updated code to take advantages of 2.3 new features +* Only convert attributes on the request that actually exist. + +1.1.0 (2013-07-04) +------------------ + +* Support for any ``Psr\Log\LoggerInterface`` as opposed to the monolog-bridge + one. +* Made dispatcher proxy methods ``on``, ``before``, ``after`` and ``error`` + lazy, so that they will not instantiate the dispatcher early. +* Dropped support for 2.1 and 2.2 versions of Symfony. + +1.0.1 (2013-07-04) +------------------ + +* Fixed RedirectableUrlMatcher::redirect() when Silex is configured to use a logger +* Make ``DoctrineServiceProvider`` multi-db support lazy. + +1.0.0 (2013-05-03) +------------------ + +* **2013-04-12**: Added support for validators as services. + +* **2013-04-01**: Added support for host matching with symfony 2.2:: + + $app->match('/', function() { + // app-specific action + })->host('example.com'); + + $app->match('/', function ($user) { + // user-specific action + })->host('{user}.example.com'); + +* **2013-03-08**: Added support for form type extensions and guessers as + services. + +* **2013-03-08**: Added support for remember-me via the + ``RememberMeServiceProvider``. + +* **2013-02-07**: Added ``Application::sendFile()`` to ease sending + ``BinaryFileResponse``. + +* **2012-11-05**: Filters have been renamed to application middlewares in the + documentation. + +* **2012-11-05**: The ``before()``, ``after()``, ``error()``, and ``finish()`` + listener priorities now set the priority of the underlying Symfony event + instead of a custom one before. + +* **2012-11-05**: Removing the default exception handler should now be done + via its ``disable()`` method: + + Before: + + unset($app['exception_handler']); + + After: + + $app['exception_handler']->disable(); + +* **2012-07-15**: removed the ``monolog.configure`` service. Use the + ``extend`` method instead: + + Before:: + + $app['monolog.configure'] = $app->protect(function ($monolog) use ($app) { + // do something + }); + + After:: + + $app['monolog'] = $app->share($app->extend('monolog', function($monolog, $app) { + // do something + + return $monolog; + })); + + +* **2012-06-17**: ``ControllerCollection`` now takes a required route instance + as a constructor argument. + + Before:: + + $controllers = new ControllerCollection(); + + After:: + + $controllers = new ControllerCollection(new Route()); + + // or even better + $controllers = $app['controllers_factory']; + +* **2012-06-17**: added application traits for PHP 5.4 + +* **2012-06-16**: renamed ``request.default_locale`` to ``locale`` + +* **2012-06-16**: Removed the ``translator.loader`` service. See documentation + for how to use XLIFF or YAML-based translation files. + +* **2012-06-15**: removed the ``twig.configure`` service. Use the ``extend`` + method instead: + + Before:: + + $app['twig.configure'] = $app->protect(function ($twig) use ($app) { + // do something + }); + + After:: + + $app['twig'] = $app->share($app->extend('twig', function($twig, $app) { + // do something + + return $twig; + })); + +* **2012-06-13**: Added a route ``before`` middleware + +* **2012-06-13**: Renamed the route ``middleware`` to ``before`` + +* **2012-06-13**: Added an extension for the Symfony Security component + +* **2012-05-31**: Made the ``BrowserKit``, ``CssSelector``, ``DomCrawler``, + ``Finder`` and ``Process`` components optional dependencies. Projects that + depend on them (e.g. through functional tests) should add those dependencies + to their ``composer.json``. + +* **2012-05-26**: added ``boot()`` to ``ServiceProviderInterface``. + +* **2012-05-26**: Removed ``SymfonyBridgesServiceProvider``. It is now implicit + by checking the existence of the bridge. + +* **2012-05-26**: Removed the ``translator.messages`` parameter (use + ``translator.domains`` instead). + +* **2012-05-24**: Removed the ``autoloader`` service (use composer instead). + The ``*.class_path`` settings on all the built-in providers have also been + removed in favor of Composer. + +* **2012-05-21**: Changed error() to allow handling specific exceptions. + +* **2012-05-20**: Added a way to define settings on a controller collection. + +* **2012-05-20**: The Request instance is not available anymore from the + Application after it has been handled. + +* **2012-04-01**: Added ``finish`` filters. + +* **2012-03-20**: Added ``json`` helper:: + + $data = array('some' => 'data'); + $response = $app->json($data); + +* **2012-03-11**: Added route middlewares. + +* **2012-03-02**: Switched to use Composer for dependency management. + +* **2012-02-27**: Updated to Symfony 2.1 session handling. + +* **2012-01-02**: Introduced support for streaming responses. + +* **2011-09-22**: ``ExtensionInterface`` has been renamed to + ``ServiceProviderInterface``. All built-in extensions have been renamed + accordingly (for instance, ``Silex\Extension\TwigExtension`` has been + renamed to ``Silex\Provider\TwigServiceProvider``). + +* **2011-09-22**: The way reusable applications work has changed. The + ``mount()`` method now takes an instance of ``ControllerCollection`` instead + of an ``Application`` one. + + Before:: + + $app = new Application(); + $app->get('/bar', function() { return 'foo'; }); + + return $app; + + After:: + + $app = new ControllerCollection(); + $app->get('/bar', function() { return 'foo'; }); + + return $app; + +* **2011-08-08**: The controller method configuration is now done on the Controller itself + + Before:: + + $app->match('/', function () { echo 'foo'; }, 'GET|POST'); + + After:: + + $app->match('/', function () { echo 'foo'; })->method('GET|POST'); diff --git a/vendor/silex/silex/doc/conf.py b/vendor/silex/silex/doc/conf.py new file mode 100644 index 00000000..dfe355c7 --- /dev/null +++ b/vendor/silex/silex/doc/conf.py @@ -0,0 +1,17 @@ +import sys, os +from sphinx.highlighting import lexers +from pygments.lexers.web import PhpLexer + +sys.path.append(os.path.abspath('_exts')) + +extensions = [] +master_doc = 'index' +highlight_language = 'php' + +project = u'Silex' +copyright = u'2010 Fabien Potencier' + +version = '0' +release = '0.0.0' + +lexers['php'] = PhpLexer(startinline=True) diff --git a/vendor/silex/silex/doc/contributing.rst b/vendor/silex/silex/doc/contributing.rst new file mode 100644 index 00000000..34a339d8 --- /dev/null +++ b/vendor/silex/silex/doc/contributing.rst @@ -0,0 +1,34 @@ +Contributing +============ + +We are open to contributions to the Silex code. If you find a bug or want to +contribute a provider, just follow these steps: + +* Fork `the Silex repository `_; + +* Make your feature addition or bug fix; + +* Add tests for it; + +* Optionally, add some documentation; + +* `Send a pull request + `_, to the correct + target branch (1.3 for bug fixes, master for new features). + +.. note:: + + Any code you contribute must be licensed under the MIT + License. + +Writing Documentation +===================== + +The documentation is written in `reStructuredText +`_ and can be generated using `sphinx +`_. + +.. code-block:: bash + + $ cd doc + $ sphinx-build -b html . build diff --git a/vendor/silex/silex/doc/cookbook/error_handler.rst b/vendor/silex/silex/doc/cookbook/error_handler.rst new file mode 100644 index 00000000..235c263a --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/error_handler.rst @@ -0,0 +1,38 @@ +Converting Errors to Exceptions +=============================== + +Silex catches exceptions that are thrown from within a request/response cycle. +However, it does *not* catch PHP errors and notices. This recipe tells you how +to catch them by converting them to exceptions. + +Registering the ErrorHandler +---------------------------- + +The ``Symfony/Debug`` package has an ``ErrorHandler`` class that solves this +problem. It converts all errors to exceptions, and exceptions are then caught +by Silex. + +Register it by calling the static ``register`` method:: + + use Symfony\Component\Debug\ErrorHandler; + + ErrorHandler::register(); + +It is recommended that you do this as early as possible. + +Handling fatal errors +--------------------- + +To handle fatal errors, you can additionally register a global +``ExceptionHandler``:: + + use Symfony\Component\Debug\ExceptionHandler; + + ExceptionHandler::register(); + +In production you may want to disable the debug output by passing ``false`` as +the ``$debug`` argument:: + + use Symfony\Component\Debug\ExceptionHandler; + + ExceptionHandler::register(false); diff --git a/vendor/silex/silex/doc/cookbook/form_no_csrf.rst b/vendor/silex/silex/doc/cookbook/form_no_csrf.rst new file mode 100644 index 00000000..e9bf595e --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/form_no_csrf.rst @@ -0,0 +1,36 @@ +Disabling CSRF Protection on a Form using the FormExtension +=========================================================== + +The *FormExtension* provides a service for building form in your application +with the Symfony Form component. When the :doc:`CSRF Service Provider +` is registered, the *FormExtension* uses the CSRF Protection +avoiding Cross-site request forgery, a method by which a malicious user +attempts to make your legitimate users unknowingly submit data that they don't +intend to submit. + +You can find more details about CSRF Protection and CSRF token in the +`Symfony Book +`_. + +In some cases (for example, when embedding a form in an html email) you might +want not to use this protection. The easiest way to avoid this is to +understand that it is possible to give specific options to your form builder +through the ``createBuilder()`` function. + +Example +------- + +.. code-block:: php + + $form = $app['form.factory']->createBuilder('form', null, array('csrf_protection' => false)); + +That's it, your form could be submitted from everywhere without CSRF Protection. + +Going further +------------- + +This specific example showed how to change the ``csrf_protection`` in the +``$options`` parameter of the ``createBuilder()`` function. More of them could +be passed through this parameter, it is as simple as using the Symfony +``getDefaultOptions()`` method in your form classes. `See more here +`_. diff --git a/vendor/silex/silex/doc/cookbook/guard_authentication.rst b/vendor/silex/silex/doc/cookbook/guard_authentication.rst new file mode 100644 index 00000000..8774f686 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/guard_authentication.rst @@ -0,0 +1,183 @@ +How to Create a Custom Authentication System with Guard +======================================================= + +Whether you need to build a traditional login form, an API token +authentication system or you need to integrate with some proprietary +single-sign-on system, the Guard component can make it easy... and fun! + +In this example, you'll build an API token authentication system and +learn how to work with Guard. + +Step 1) Create the Authenticator Class +-------------------------------------- + +Suppose you have an API where your clients will send an X-AUTH-TOKEN +header on each request. This token is composed of the username followed +by a password, separated by a colon (e.g. ``X-AUTH-TOKEN: coolguy:awesomepassword``). +Your job is to read this, find the associated user (if any) and check +the password. + +To create a custom authentication system, just create a class and make +it implement GuardAuthenticatorInterface. Or, extend the simpler +AbstractGuardAuthenticator. This requires you to implement six methods: + +.. code-block:: php + + encoderFactory = $encoderFactory; + } + + public function getCredentials(Request $request) + { + // Checks if the credential header is provided + if (!$token = $request->headers->get('X-AUTH-TOKEN')) { + return; + } + + // Parse the header or ignore it if the format is incorrect. + if (false === strpos($token, ':')) { + return; + } + list($username, $secret) = explode(':', $token, 2); + + return array( + 'username' => $username, + 'secret' => $secret, + ); + } + + public function getUser($credentials, UserProviderInterface $userProvider) + { + return $userProvider->loadUserByUsername($credentials['username']); + } + + public function checkCredentials($credentials, UserInterface $user) + { + // check credentials - e.g. make sure the password is valid + // return true to cause authentication success + + $encoder = $this->encoderFactory->getEncoder($user); + + return $encoder->isPasswordValid( + $user->getPassword(), + $credentials['secret'], + $user->getSalt() + ); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) + { + // on success, let the request continue + return; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception) + { + $data = array( + 'message' => strtr($exception->getMessageKey(), $exception->getMessageData()), + + // or to translate this message + // $this->translator->trans($exception->getMessageKey(), $exception->getMessageData()) + ); + + return new JsonResponse($data, 403); + } + + /** + * Called when authentication is needed, but it's not sent + */ + public function start(Request $request, AuthenticationException $authException = null) + { + $data = array( + // you might translate this message + 'message' => 'Authentication Required', + ); + + return new JsonResponse($data, 401); + } + + public function supportsRememberMe() + { + return false; + } + } + + +Step 2) Configure the Authenticator +----------------------------------- + +To finish this, register the class as a service: + +.. code-block:: php + + $app['app.token_authenticator'] = function ($app) { + return new App\Security\TokenAuthenticator($app['security.encoder_factory']); + }; + + +Finally, configure your `security.firewalls` key to use this authenticator: + +.. code-block:: php + + $app['security.firewalls'] = array( + 'main' => array( + 'guard' => array( + 'authenticators' => array( + 'app.token_authenticator' + ), + + // Using more than 1 authenticator, you must specify + // which one is used as entry point. + // 'entry_point' => 'app.token_authenticator', + ), + // configure where your users come from. Hardcode them, or load them from somewhere + // http://silex.sensiolabs.org/doc/providers/security.html#defining-a-custom-user-provider + 'users' => array( + //raw password = foo + 'victoria' => array('ROLE_USER', '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a'), + ), + // 'anonymous' => true + ), + ); + +.. note:: + You can use many authenticators, they are executed by the order + they are configured. + +You did it! You now have a fully-working API token authentication +system. If your homepage required ROLE_USER, then you could test it +under different conditions: + +.. code-block:: bash + + # test with no token + curl http://localhost:8000/ + # {"message":"Authentication Required"} + + # test with a bad token + curl -H "X-AUTH-TOKEN: alan" http://localhost:8000/ + # {"message":"Username could not be found."} + + # test with a working token + curl -H "X-AUTH-TOKEN: victoria:foo" http://localhost:8000/ + # the homepage controller is executed: the page loads normally + +For more details read the Symfony cookbook entry on +`How to Create a Custom Authentication System with Guard `_. diff --git a/vendor/silex/silex/doc/cookbook/index.rst b/vendor/silex/silex/doc/cookbook/index.rst new file mode 100644 index 00000000..53b10fe2 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/index.rst @@ -0,0 +1,40 @@ +Cookbook +======== + +The cookbook section contains recipes for solving specific problems. + +.. toctree:: + :maxdepth: 1 + :hidden: + + json_request_body + session_storage + form_no_csrf + validator_yaml + sub_requests + error_handler + multiple_loggers + guard_authentication + +Recipes +------- + +* :doc:`Accepting a JSON Request Body ` A common need when + building a restful API is the ability to accept a JSON encoded entity from + the request body. + +* :doc:`Using PdoSessionStorage to store Sessions in the Database + `. + +* :doc:`Disabling the CSRF Protection on a Form using the FormExtension + `. + +* :doc:`Using YAML to configure Validation `. + +* :doc:`Making sub-Requests `. + +* :doc:`Converting Errors to Exceptions `. + +* :doc:`Using multiple Monolog Loggers `. + +* :doc:`How to Create a Custom Authentication System with Guard `. diff --git a/vendor/silex/silex/doc/cookbook/json_request_body.rst b/vendor/silex/silex/doc/cookbook/json_request_body.rst new file mode 100644 index 00000000..47159008 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/json_request_body.rst @@ -0,0 +1,95 @@ +Accepting a JSON Request Body +============================= + +A common need when building a restful API is the ability to accept a JSON +encoded entity from the request body. + +An example for such an API could be a blog post creation. + +Example API +----------- + +In this example we will create an API for creating a blog post. The following +is a spec of how we want it to work. + +Request +~~~~~~~ + +In the request we send the data for the blog post as a JSON object. We also +indicate that using the ``Content-Type`` header: + +.. code-block:: text + + POST /blog/posts + Accept: application/json + Content-Type: application/json + Content-Length: 57 + + {"title":"Hello World!","body":"This is my first post!"} + +Response +~~~~~~~~ + +The server responds with a 201 status code, telling us that the post was +created. It tells us the ``Content-Type`` of the response, which is also +JSON: + +.. code-block:: text + + HTTP/1.1 201 Created + Content-Type: application/json + Content-Length: 65 + Connection: close + + {"id":"1","title":"Hello World!","body":"This is my first post!"} + +Parsing the request body +------------------------ + +The request body should only be parsed as JSON if the ``Content-Type`` header +begins with ``application/json``. Since we want to do this for every request, +the easiest solution is to use an application before middleware. + +We simply use ``json_decode`` to parse the content of the request and then +replace the request data on the ``$request`` object:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\ParameterBag; + + $app->before(function (Request $request) { + if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) { + $data = json_decode($request->getContent(), true); + $request->request->replace(is_array($data) ? $data : array()); + } + }); + +Controller implementation +------------------------- + +Our controller will create a new blog post from the data provided and will +return the post object, including its ``id``, as JSON:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + $app->post('/blog/posts', function (Request $request) use ($app) { + $post = array( + 'title' => $request->request->get('title'), + 'body' => $request->request->get('body'), + ); + + $post['id'] = createPost($post); + + return $app->json($post, 201); + }); + +Manual testing +-------------- + +In order to manually test our API, we can use the ``curl`` command line +utility, which allows sending HTTP requests: + +.. code-block:: bash + + $ curl http://blog.lo/blog/posts -d '{"title":"Hello World!","body":"This is my first post!"}' -H 'Content-Type: application/json' + {"id":"1","title":"Hello World!","body":"This is my first post!"} diff --git a/vendor/silex/silex/doc/cookbook/multiple_loggers.rst b/vendor/silex/silex/doc/cookbook/multiple_loggers.rst new file mode 100644 index 00000000..cb103953 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/multiple_loggers.rst @@ -0,0 +1,69 @@ +Using multiple Monolog Loggers +============================== + +Having separate instances of Monolog for different parts of your system is +often desirable and allows you to configure them independently, allowing for fine +grained control of where your logging goes and in what detail. + +This simple example allows you to quickly configure several monolog instances, +using the bundled handler, but each with a different channel. + +.. code-block:: php + + $app['monolog.factory'] = $app->protect(function ($name) use ($app) { + $log = new $app['monolog.logger.class']($name); + $log->pushHandler($app['monolog.handler']); + + return $log; + }); + + foreach (array('auth', 'payments', 'stats') as $channel) { + $app['monolog.'.$channel] = function ($app) use ($channel) { + return $app['monolog.factory']($channel); + }; + } + +As your application grows, or your logging needs for certain areas of the +system become apparent, it should be straightforward to then configure that +particular service separately, including your customizations. + +.. code-block:: php + + use Monolog\Handler\StreamHandler; + + $app['monolog.payments'] = function ($app) { + $log = new $app['monolog.logger.class']('payments'); + $handler = new StreamHandler($app['monolog.payments.logfile'], $app['monolog.payment.level']); + $log->pushHandler($handler); + + return $log; + }; + +Alternatively, you could attempt to make the factory more complicated, and rely +on some conventions, such as checking for an array of handlers registered with +the container with the channel name, defaulting to the bundled handler. + +.. code-block:: php + + use Monolog\Handler\StreamHandler; + use Monolog\Logger; + + $app['monolog.factory'] = $app->protect(function ($name) use ($app) { + $log = new $app['monolog.logger.class']($name); + + $handlers = isset($app['monolog.'.$name.'.handlers']) + ? $app['monolog.'.$name.'.handlers'] + : array($app['monolog.handler']); + + foreach ($handlers as $handler) { + $log->pushHandler($handler); + } + + return $log; + }); + + $app['monolog.payments.handlers'] = function ($app) { + return array( + new StreamHandler(__DIR__.'/../payments.log', Logger::DEBUG), + ); + }; diff --git a/vendor/silex/silex/doc/cookbook/session_storage.rst b/vendor/silex/silex/doc/cookbook/session_storage.rst new file mode 100644 index 00000000..29328b49 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/session_storage.rst @@ -0,0 +1,89 @@ +Using PdoSessionStorage to store Sessions in the Database +========================================================= + +By default, the :doc:`SessionServiceProvider ` writes +session information in files using Symfony NativeFileSessionStorage. Most +medium to large websites use a database to store sessions instead of files, +because databases are easier to use and scale in a multi-webserver environment. + +Symfony's `NativeSessionStorage +`_ +has multiple storage handlers and one of them uses PDO to store sessions, +`PdoSessionHandler +`_. +To use it, replace the ``session.storage.handler`` service in your application +like explained below. + +With a dedicated PDO service +---------------------------- + +.. code-block:: php + + use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + + $app->register(new Silex\Provider\SessionServiceProvider()); + + $app['pdo.dsn'] = 'mysql:dbname=mydatabase'; + $app['pdo.user'] = 'myuser'; + $app['pdo.password'] = 'mypassword'; + + $app['session.db_options'] = array( + 'db_table' => 'session', + 'db_id_col' => 'session_id', + 'db_data_col' => 'session_value', + 'db_time_col' => 'session_time', + ); + + $app['pdo'] = function () use ($app) { + return new PDO( + $app['pdo.dsn'], + $app['pdo.user'], + $app['pdo.password'] + ); + }; + + $app['session.storage.handler'] = function () use ($app) { + return new PdoSessionHandler( + $app['pdo'], + $app['session.db_options'] + ); + }; + +Using the DoctrineServiceProvider +--------------------------------- + +When using the :doc:`DoctrineServiceProvider ` You don't +have to make another database connection, simply pass the getWrappedConnection method. + +.. code-block:: php + + use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + + $app->register(new Silex\Provider\SessionServiceProvider()); + + $app['session.storage.handler'] = function () use ($app) { + return new PdoSessionHandler( + $app['db']->getWrappedConnection(), + array( + 'db_table' => 'session', + 'db_id_col' => 'session_id', + 'db_data_col' => 'session_value', + 'db_lifetime_col' => 'session_lifetime', + 'db_time_col' => 'session_time', + ) + ); + }; + +Database structure +------------------ + +PdoSessionStorage needs a database table with 3 columns: + +* ``session_id``: ID column (VARCHAR(255) or larger) +* ``session_value``: Value column (TEXT or CLOB) +* ``session_lifetime``: Lifetime column (INTEGER) +* ``session_time``: Time column (INTEGER) + +You can find examples of SQL statements to create the session table in the +`Symfony cookbook +`_ diff --git a/vendor/silex/silex/doc/cookbook/sub_requests.rst b/vendor/silex/silex/doc/cookbook/sub_requests.rst new file mode 100644 index 00000000..95d39136 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/sub_requests.rst @@ -0,0 +1,137 @@ +Making sub-Requests +=================== + +Since Silex is based on the ``HttpKernelInterface``, it allows you to simulate +requests against your application. This means that you can embed a page within +another, it also allows you to forward a request which is essentially an +internal redirect that does not change the URL. + +Basics +------ + +You can make a sub-request by calling the ``handle`` method on the +``Application``. This method takes three arguments: + +* ``$request``: An instance of the ``Request`` class which represents the + HTTP request. + +* ``$type``: Must be either ``HttpKernelInterface::MASTER_REQUEST`` or + ``HttpKernelInterface::SUB_REQUEST``. Certain listeners are only executed for + the master request, so it's important that this is set to ``SUB_REQUEST``. + +* ``$catch``: Catches exceptions and turns them into a response with status code + ``500``. This argument defaults to ``true``. For sub-requests you will most + likely want to set it to ``false``. + +By calling ``handle``, you can make a sub-request manually. Here's an example:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpKernel\HttpKernelInterface; + + $subRequest = Request::create('/'); + $response = $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); + +There's some more things that you need to keep in mind though. In most cases +you will want to forward some parts of the current master request to the +sub-request like cookies, server information, or the session. + +Here is a more advanced example that forwards said information (``$request`` +holds the master request):: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpKernel\HttpKernelInterface; + + $subRequest = Request::create('/', 'GET', array(), $request->cookies->all(), array(), $request->server->all()); + if ($request->getSession()) { + $subRequest->setSession($request->getSession()); + } + + $response = $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); + +To forward this response to the client, you can simply return it from a +controller:: + + use Silex\Application; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpKernel\HttpKernelInterface; + + $app->get('/foo', function (Application $app, Request $request) { + $subRequest = Request::create('/', ...); + $response = $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); + + return $response; + }); + +If you want to embed the response as part of a larger page you can call +``Response::getContent``:: + + $header = ...; + $footer = ...; + $body = $response->getContent(); + + return $header.$body.$footer; + +Rendering pages in Twig templates +--------------------------------- + +The :doc:`TwigServiceProvider ` provides a ``render`` +function that you can use in Twig templates. It gives you a convenient way to +embed pages. + +.. code-block:: jinja + + {{ render('/sidebar') }} + +For details, refer to the :doc:`TwigServiceProvider ` docs. + +Edge Side Includes +------------------ + +You can use ESI either through the :doc:`HttpCacheServiceProvider +` or a reverse proxy cache such as Varnish. This also +allows you to embed pages, however it also gives you the benefit of caching +parts of the page. + +Here is an example of how you would embed a page via ESI: + +.. code-block:: jinja + + + +For details, refer to the :doc:`HttpCacheServiceProvider +` docs. + +Dealing with the request base URL +--------------------------------- + +One thing to watch out for is the base URL. If your application is not +hosted at the webroot of your web server, then you may have an URL like +``http://example.org/foo/index.php/articles/42``. + +In this case, ``/foo/index.php`` is your request base path. Silex accounts for +this path prefix in the routing process, it reads it from +``$request->server``. In the context of sub-requests this can lead to issues, +because if you do not prepend the base path the request could mistake a part +of the path you want to match as the base path and cut it off. + +You can prevent that from happening by always prepending the base path when +constructing a request:: + + $url = $request->getUriForPath('/'); + $subRequest = Request::create($url, 'GET', array(), $request->cookies->all(), array(), $request->server->all()); + +This is something to be aware of when making sub-requests by hand. + +Services depending on the Request +--------------------------------- + +The container is a concept that is global to a Silex application, since the +application object **is** the container. Any request that is run against an +application will re-use the same set of services. Since these services are +mutable, code in a master request can affect the sub-requests and vice versa. +Any services depending on the ``request`` service will store the first request +that they get (could be master or sub-request), and keep using it, even if +that request is already over. + +Instead of injecting the ``request`` service, you should always inject the +``request_stack`` one instead. diff --git a/vendor/silex/silex/doc/cookbook/validator_yaml.rst b/vendor/silex/silex/doc/cookbook/validator_yaml.rst new file mode 100644 index 00000000..10a41fcf --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/validator_yaml.rst @@ -0,0 +1,35 @@ +Using YAML to configure Validation +================================== + +Simplicity is at the heart of Silex so there is no out of the box solution to +use YAML files for validation. But this doesn't mean that this is not +possible. Let's see how to do it. + +First, you need to install the YAML Component: + +.. code-block:: bash + + composer require symfony/yaml + +Next, you need to tell the Validation Service that you are not using +``StaticMethodLoader`` to load your class metadata but a YAML file:: + + $app->register(new ValidatorServiceProvider()); + + $app['validator.mapping.class_metadata_factory'] = new Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory( + new Symfony\Component\Validator\Mapping\Loader\YamlFileLoader(__DIR__.'/validation.yml') + ); + +Now, we can replace the usage of the static method and move all the validation +rules to ``validation.yml``: + +.. code-block:: yaml + + # validation.yml + Post: + properties: + title: + - NotNull: ~ + - NotBlank: ~ + body: + - Min: 100 diff --git a/vendor/silex/silex/doc/index.rst b/vendor/silex/silex/doc/index.rst new file mode 100644 index 00000000..d1a851d0 --- /dev/null +++ b/vendor/silex/silex/doc/index.rst @@ -0,0 +1,19 @@ +The Book +======== + +.. toctree:: + :maxdepth: 1 + + intro + usage + middlewares + organizing_controllers + services + providers + testing + cookbook/index + internals + contributing + providers/index + web_servers + changelog diff --git a/vendor/silex/silex/doc/internals.rst b/vendor/silex/silex/doc/internals.rst new file mode 100644 index 00000000..c7ffac8c --- /dev/null +++ b/vendor/silex/silex/doc/internals.rst @@ -0,0 +1,84 @@ +Internals +========= + +This chapter will tell you how Silex works internally. + +Silex +----- + +Application +~~~~~~~~~~~ + +The application is the main interface to Silex. It implements Symfony's +`HttpKernelInterface +`_, +so you can pass a `Request +`_ +to the ``handle`` method and it will return a `Response +`_. + +It extends the ``Pimple`` service container, allowing for flexibility on the +outside as well as the inside. You could replace any service, and you are also +able to read them. + +The application makes strong use of the `EventDispatcher +`_ to hook into the Symfony `HttpKernel +`_ +events. This allows fetching the ``Request``, converting string responses into +``Response`` objects and handling Exceptions. We also use it to dispatch some +custom events like before/after middlewares and errors. + +Controller +~~~~~~~~~~ + +The Symfony `Route +`_ is +actually quite powerful. Routes can be named, which allows for URL generation. +They can also have requirements for the variable parts. In order to allow +setting these through a nice interface, the ``match`` method (which is used by +``get``, ``post``, etc.) returns an instance of the ``Controller``, which +wraps a route. + +ControllerCollection +~~~~~~~~~~~~~~~~~~~~ + +One of the goals of exposing the `RouteCollection +`_ +was to make it mutable, so providers could add stuff to it. The challenge here +is the fact that routes know nothing about their name. The name only has +meaning in context of the ``RouteCollection`` and cannot be changed. + +To solve this challenge we came up with a staging area for routes. The +``ControllerCollection`` holds the controllers until ``flush`` is called, at +which point the routes are added to the ``RouteCollection``. Also, the +controllers are then frozen. This means that they can no longer be modified +and will throw an Exception if you try to do so. + +Unfortunately no good way for flushing implicitly could be found, which is why +flushing is now always explicit. The Application will flush, but if you want +to read the ``ControllerCollection`` before the request takes place, you will +have to call flush yourself. + +The ``Application`` provides a shortcut ``flush`` method for flushing the +``ControllerCollection``. + +.. tip:: + + Instead of creating an instance of ``RouteCollection`` yourself, use the + ``$app['controllers_factory']`` factory instead. + +Symfony +------- + +Following Symfony components are used by Silex: + +* **HttpFoundation**: For ``Request`` and ``Response``. + +* **HttpKernel**: Because we need a heart. + +* **Routing**: For matching defined routes. + +* **EventDispatcher**: For hooking into the HttpKernel. + +For more information, `check out the Symfony website `_. diff --git a/vendor/silex/silex/doc/intro.rst b/vendor/silex/silex/doc/intro.rst new file mode 100644 index 00000000..2ab2bc30 --- /dev/null +++ b/vendor/silex/silex/doc/intro.rst @@ -0,0 +1,50 @@ +Introduction +============ + +Silex is a PHP microframework. It is built on the shoulders of `Symfony`_ and +`Pimple`_ and also inspired by `Sinatra`_. + +Silex aims to be: + +* *Concise*: Silex exposes an intuitive and concise API. + +* *Extensible*: Silex has an extension system based around the Pimple + service-container that makes it easy to tie in third party libraries. + +* *Testable*: Silex uses Symfony's HttpKernel which abstracts request and + response. This makes it very easy to test apps and the framework itself. It + also respects the HTTP specification and encourages its proper use. + +In a nutshell, you define controllers and map them to routes, all in one step. + +Usage +----- + +.. code-block:: php + + get('/hello/{name}', function ($name) use ($app) { + return 'Hello '.$app->escape($name); + }); + + $app->run(); + +All that is needed to get access to the Framework is to include the +autoloader. + +Next, a route for ``/hello/{name}`` that matches for ``GET`` requests is +defined. When the route matches, the function is executed and the return value +is sent back to the client. + +Finally, the app is run. Visit ``/hello/world`` to see the result. It's really +that easy! + +.. _Symfony: http://symfony.com/ +.. _Pimple: http://pimple.sensiolabs.org/ +.. _Sinatra: http://www.sinatrarb.com/ diff --git a/vendor/silex/silex/doc/middlewares.rst b/vendor/silex/silex/doc/middlewares.rst new file mode 100644 index 00000000..c5c17cf0 --- /dev/null +++ b/vendor/silex/silex/doc/middlewares.rst @@ -0,0 +1,162 @@ +Middleware +========== + +Silex allows you to run code, that changes the default Silex behavior, at +different stages during the handling of a request through *middleware*: + +* *Application middleware* is triggered independently of the current handled + request; + +* *Route middleware* is triggered when its associated route is matched. + +Application Middleware +---------------------- + +Application middleware is only run for the "master" Request. + +Before Middleware +~~~~~~~~~~~~~~~~~ + +A *before* application middleware allows you to tweak the Request before the +controller is executed:: + + $app->before(function (Request $request, Application $app) { + // ... + }); + +By default, the middleware is run after the routing and the security. + +If you want your middleware to be run even if an exception is thrown early on +(on a 404 or 403 error for instance), then, you need to register it as an +early event:: + + $app->before(function (Request $request, Application $app) { + // ... + }, Application::EARLY_EVENT); + +In this case, the routing and the security won't have been executed, and so you +won't have access to the locale, the current route, or the security user. + +.. note:: + + The before middleware is an event registered on the Symfony *request* + event. + +After Middleware +~~~~~~~~~~~~~~~~ + +An *after* application middleware allows you to tweak the Response before it +is sent to the client:: + + $app->after(function (Request $request, Response $response) { + // ... + }); + +.. note:: + + The after middleware is an event registered on the Symfony *response* + event. + +Finish Middleware +~~~~~~~~~~~~~~~~~ + +A *finish* application middleware allows you to execute tasks after the +Response has been sent to the client (like sending emails or logging):: + + $app->finish(function (Request $request, Response $response) { + // ... + // Warning: modifications to the Request or Response will be ignored + }); + +.. note:: + + The finish middleware is an event registered on the Symfony *terminate* + event. + +Route Middleware +---------------- + +Route middleware is added to routes or route collections and it is only +triggered when the corresponding route is matched. You can also stack them:: + + $app->get('/somewhere', function () { + // ... + }) + ->before($before1) + ->before($before2) + ->after($after1) + ->after($after2) + ; + +Before Middleware +~~~~~~~~~~~~~~~~~ + +A *before* route middleware is fired just before the route callback, but after +the *before* application middleware:: + + $before = function (Request $request, Application $app) { + // ... + }; + + $app->get('/somewhere', function () { + // ... + }) + ->before($before); + +After Middleware +~~~~~~~~~~~~~~~~ + +An *after* route middleware is fired just after the route callback, but before +the application *after* application middleware:: + + $after = function (Request $request, Response $response, Application $app) { + // ... + }; + + $app->get('/somewhere', function () { + // ... + }) + ->after($after); + +Middleware Priority +------------------- + +You can add as much middleware as you want, in which case they are triggered +in the same order as you added them. + +You can explicitly control the priority of your middleware by passing an +additional argument to the registration methods:: + + $app->before(function (Request $request) { + // ... + }, 32); + +As a convenience, two constants allow you to register an event as early as +possible or as late as possible:: + + $app->before(function (Request $request) { + // ... + }, Application::EARLY_EVENT); + + $app->before(function (Request $request) { + // ... + }, Application::LATE_EVENT); + +Short-circuiting the Controller +------------------------------- + +If a *before* middleware returns a ``Response`` object, the request handling is +short-circuited (the next middleware won't be run, nor the route +callback), and the Response is passed to the *after* middleware right away:: + + $app->before(function (Request $request) { + // redirect the user to the login screen if access to the Resource is protected + if (...) { + return new RedirectResponse('/login'); + } + }); + +.. note:: + + A ``RuntimeException`` is thrown if a before middleware does not return a + Response or ``null``. diff --git a/vendor/silex/silex/doc/organizing_controllers.rst b/vendor/silex/silex/doc/organizing_controllers.rst new file mode 100644 index 00000000..50558cbb --- /dev/null +++ b/vendor/silex/silex/doc/organizing_controllers.rst @@ -0,0 +1,84 @@ +Organizing Controllers +====================== + +When your application starts to define too many controllers, you might want to +group them logically:: + + // define controllers for a blog + $blog = $app['controllers_factory']; + $blog->get('/', function () { + return 'Blog home page'; + }); + // ... + + // define controllers for a forum + $forum = $app['controllers_factory']; + $forum->get('/', function () { + return 'Forum home page'; + }); + + // define "global" controllers + $app->get('/', function () { + return 'Main home page'; + }); + + $app->mount('/blog', $blog); + $app->mount('/forum', $forum); + + // define controllers for a admin + $app->mount('/admin', function ($admin) { + // recursively mount + $admin->mount('/blog', function ($user) { + $user->get('/', function () { + return 'Admin Blog home page'; + }); + }); + }); + +.. note:: + + ``$app['controllers_factory']`` is a factory that returns a new instance + of ``ControllerCollection`` when used. + +``mount()`` prefixes all routes with the given prefix and merges them into the +main Application. So, ``/`` will map to the main home page, ``/blog/`` to the +blog home page, ``/forum/`` to the forum home page, and ``/admin/blog/`` to the +admin blog home page. + +.. caution:: + + When mounting a route collection under ``/blog``, it is not possible to + define a route for the ``/blog`` URL. The shortest possible URL is + ``/blog/``. + +.. note:: + + When calling ``get()``, ``match()``, or any other HTTP methods on the + Application, you are in fact calling them on a default instance of + ``ControllerCollection`` (stored in ``$app['controllers']``). + +Another benefit is the ability to apply settings on a set of controllers very +easily. Building on the example from the middleware section, here is how you +would secure all controllers for the backend collection:: + + $backend = $app['controllers_factory']; + + // ensure that all controllers require logged-in users + $backend->before($mustBeLogged); + +.. tip:: + + For a better readability, you can split each controller collection into a + separate file:: + + // blog.php + $blog = $app['controllers_factory']; + $blog->get('/', function () { return 'Blog home page'; }); + + return $blog; + + // app.php + $app->mount('/blog', include 'blog.php'); + + Instead of requiring a file, you can also create a :ref:`Controller + provider `. diff --git a/vendor/silex/silex/doc/providers.rst b/vendor/silex/silex/doc/providers.rst new file mode 100644 index 00000000..c3d049db --- /dev/null +++ b/vendor/silex/silex/doc/providers.rst @@ -0,0 +1,262 @@ +Providers +========= + +Providers allow the developer to reuse parts of an application into another +one. Silex provides two types of providers defined by two interfaces: +``ServiceProviderInterface`` for services and ``ControllerProviderInterface`` +for controllers. + +Service Providers +----------------- + +Loading providers +~~~~~~~~~~~~~~~~~ + +In order to load and use a service provider, you must register it on the +application:: + + $app = new Silex\Application(); + + $app->register(new Acme\DatabaseServiceProvider()); + +You can also provide some parameters as a second argument. These will be set +**after** the provider is registered, but **before** it is booted:: + + $app->register(new Acme\DatabaseServiceProvider(), array( + 'database.dsn' => 'mysql:host=localhost;dbname=myapp', + 'database.user' => 'root', + 'database.password' => 'secret_root_password', + )); + +Conventions +~~~~~~~~~~~ + +You need to watch out in what order you do certain things when interacting +with providers. Just keep these rules in mind: + +* Overriding existing services must occur **after** the provider is + registered. + + *Reason: If the service already exists, the provider will overwrite it.* + +* You can set parameters any time **after** the provider is registered, but + **before** the service is accessed. + + *Reason: Providers can set default values for parameters. Just like with + services, the provider will overwrite existing values.* + +Included providers +~~~~~~~~~~~~~~~~~~ + +There are a few providers that you get out of the box. All of these are within +the ``Silex\Provider`` namespace: + +* :doc:`AssetServiceProvider ` +* :doc:`CsrfServiceProvider ` +* :doc:`DoctrineServiceProvider ` +* :doc:`FormServiceProvider ` +* :doc:`HttpCacheServiceProvider ` +* :doc:`HttpFragmentServiceProvider ` +* :doc:`LocaleServiceProvider ` +* :doc:`MonologServiceProvider ` +* :doc:`RememberMeServiceProvider ` +* :doc:`SecurityServiceProvider ` +* :doc:`SerializerServiceProvider ` +* :doc:`ServiceControllerServiceProvider ` +* :doc:`SessionServiceProvider ` +* :doc:`SwiftmailerServiceProvider ` +* :doc:`TranslationServiceProvider ` +* :doc:`TwigServiceProvider ` +* :doc:`ValidatorServiceProvider ` +* :doc:`VarDumperServiceProvider ` + +.. note:: + + The Silex core team maintains a `WebProfiler + `_ provider that helps debug + code in the development environment thanks to the Symfony web debug toolbar + and the Symfony profiler. + +Third party providers +~~~~~~~~~~~~~~~~~~~~~ + +Some service providers are developed by the community. Those third-party +providers are listed on `Silex' repository wiki +`_. + +You are encouraged to share yours. + +Creating a provider +~~~~~~~~~~~~~~~~~~~ + +Providers must implement the ``Pimple\ServiceProviderInterface``:: + + interface ServiceProviderInterface + { + public function register(Container $container); + } + +This is very straight forward, just create a new class that implements the +register method. In the ``register()`` method, you can define services on the +application which then may make use of other services and parameters. + +.. tip:: + + The ``Pimple\ServiceProviderInterface`` belongs to the Pimple package, so + take care to only use the API of ``Pimple\Container`` within your + ``register`` method. Not only is this a good practice due to the way Pimple + and Silex work, but may allow your provider to be used outside of Silex. + +Optionally, your service provider can implement the +``Silex\Api\BootableProviderInterface``. A bootable provider must +implement the ``boot()`` method, with which you can configure the application, just +before it handles a request:: + + interface BootableProviderInterface + { + function boot(Application $app); + } + +Another optional interface, is the ``Silex\Api\EventListenerProviderInterface``. +This interface contains the ``subscribe()`` method, which allows your provider to +subscribe event listener with Silex's EventDispatcher, just before it handles a +request:: + + interface EventListenerProviderInterface + { + function subscribe(Container $app, EventDispatcherInterface $dispatcher); + } + +Here is an example of such a provider:: + + namespace Acme; + + use Pimple\Container; + use Pimple\ServiceProviderInterface; + use Silex\Application; + use Silex\Api\BootableProviderInterface; + use Silex\Api\EventListenerProviderInterface; + use Symfony\Component\EventDispatcher\EventDispatcherInterface; + use Symfony\Component\HttpKernel\KernelEvents; + use Symfony\Component\HttpKernel\Event\FilterResponseEvent; + + class HelloServiceProvider implements ServiceProviderInterface, BootableProviderInterface, EventListenerProviderInterface + { + public function register(Container $app) + { + $app['hello'] = $app->protect(function ($name) use ($app) { + $default = $app['hello.default_name'] ? $app['hello.default_name'] : ''; + $name = $name ?: $default; + + return 'Hello '.$app->escape($name); + }); + } + + public function boot(Application $app) + { + // do something + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addListener(KernelEvents::REQUEST, function(FilterResponseEvent $event) use ($app) { + // do something + }); + } + } + +This class provides a ``hello`` service which is a protected closure. It takes +a ``name`` argument and will return ``hello.default_name`` if no name is +given. If the default is also missing, it will use an empty string. + +You can now use this provider as follows:: + + use Symfony\Component\HttpFoundation\Request; + + $app = new Silex\Application(); + + $app->register(new Acme\HelloServiceProvider(), array( + 'hello.default_name' => 'Igor', + )); + + $app->get('/hello', function (Request $request) use ($app) { + $name = $request->get('name'); + + return $app['hello']($name); + }); + +In this example we are getting the ``name`` parameter from the query string, +so the request path would have to be ``/hello?name=Fabien``. + +.. _controller-providers: + +Controller Providers +-------------------- + +Loading providers +~~~~~~~~~~~~~~~~~ + +In order to load and use a controller provider, you must "mount" its +controllers under a path:: + + $app = new Silex\Application(); + + $app->mount('/blog', new Acme\BlogControllerProvider()); + +All controllers defined by the provider will now be available under the +``/blog`` path. + +Creating a provider +~~~~~~~~~~~~~~~~~~~ + +Providers must implement the ``Silex\Api\ControllerProviderInterface``:: + + interface ControllerProviderInterface + { + public function connect(Application $app); + } + +Here is an example of such a provider:: + + namespace Acme; + + use Silex\Application; + use Silex\Api\ControllerProviderInterface; + + class HelloControllerProvider implements ControllerProviderInterface + { + public function connect(Application $app) + { + // creates a new controller based on the default route + $controllers = $app['controllers_factory']; + + $controllers->get('/', function (Application $app) { + return $app->redirect('/hello'); + }); + + return $controllers; + } + } + +The ``connect`` method must return an instance of ``ControllerCollection``. +``ControllerCollection`` is the class where all controller related methods are +defined (like ``get``, ``post``, ``match``, ...). + +.. tip:: + + The ``Application`` class acts in fact as a proxy for these methods. + +You can use this provider as follows:: + + $app = new Silex\Application(); + + $app->mount('/blog', new Acme\HelloControllerProvider()); + +In this example, the ``/blog/`` path now references the controller defined in +the provider. + +.. tip:: + + You can also define a provider that implements both the service and the + controller provider interface and package in the same class the services + needed to make your controllers work. diff --git a/vendor/silex/silex/doc/providers/asset.rst b/vendor/silex/silex/doc/providers/asset.rst new file mode 100644 index 00000000..72c3d703 --- /dev/null +++ b/vendor/silex/silex/doc/providers/asset.rst @@ -0,0 +1,67 @@ +Asset +===== + +The *AssetServiceProvider* provides a way to manage URL generation and +versioning of web assets such as CSS stylesheets, JavaScript files and image +files. + +Parameters +---------- + +* **assets.version**: Default version for assets. + +* **assets.format_version** (optional): Default format for assets. + +* **assets.named_packages** (optional): Named packages. Keys are the package + names and values the configuration (supported keys are ``version``, + ``version_format``, ``base_urls``, and ``base_path``). + +Services +-------- + +* **assets.packages**: The asset service. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\AssetServiceProvider(), array( + 'assets.version' => 'v1', + 'assets.version_format' => '%s?version=%s', + 'assets.named_packages' => array( + 'css' => array('version' => 'css2', 'base_path' => '/whatever-makes-sense'), + 'images' => array('base_urls' => array('https://img.example.com')), + ), + )); + +.. note:: + + Add the Symfony Asset Component as a dependency: + + .. code-block:: bash + + composer require symfony/asset + + If you want to use assets in your Twig templates, you must also install the + Symfony Twig Bridge: + + .. code-block:: bash + + composer require symfony/twig-bridge + +Usage +----- + +The AssetServiceProvider is mostly useful with the Twig provider: + +.. code-block:: jinja + + {{ asset('/css/foo.png') }} + {{ asset('/css/foo.css', 'css') }} + {{ asset('/img/foo.png', 'images') }} + + {{ asset_version('/css/foo.png') }} + +For more information, check out the `Asset Component documentation +`_. diff --git a/vendor/silex/silex/doc/providers/csrf.rst b/vendor/silex/silex/doc/providers/csrf.rst new file mode 100644 index 00000000..3bd35f4b --- /dev/null +++ b/vendor/silex/silex/doc/providers/csrf.rst @@ -0,0 +1,52 @@ +CSRF +==== + +The *CsrfServiceProvider* provides a service for building forms in your +application with the Symfony Form component. + +Parameters +---------- + +* none + +Services +-------- + +* **csrf.token_manager**: An instance of an implementation of the + `CsrfProviderInterface + `_, + defaults to a `DefaultCsrfProvider + `_. + +Registering +----------- + +.. code-block:: php + + use Silex\Provider\CsrfServiceProvider; + + $app->register(new CsrfServiceProvider()); + +.. note:: + + Add the Symfony's `Security CSRF Component + `_ as a + dependency: + + .. code-block:: bash + + composer require symfony/security-csrf + +Usage +----- + +When the CSRF Service Provider is registered, all forms created via the Form +Service Provider are protected against CSRF by default. + +You can also use the CSRF protection even without using the Symfony Form +component. If, for example, you're doing a DELETE action, you can check the +CSRF token:: + + use Symfony\Component\Security\Csrf\CsrfToken; + + $app['csrf.token_manager']->isTokenValid(new CsrfToken('token_id', 'TOKEN')); diff --git a/vendor/silex/silex/doc/providers/doctrine.rst b/vendor/silex/silex/doc/providers/doctrine.rst new file mode 100644 index 00000000..0ef167b7 --- /dev/null +++ b/vendor/silex/silex/doc/providers/doctrine.rst @@ -0,0 +1,137 @@ +Doctrine +======== + +The *DoctrineServiceProvider* provides integration with the `Doctrine DBAL +`_ for easy database access +(Doctrine ORM integration is **not** supplied). + +Parameters +---------- + +* **db.options**: Array of Doctrine DBAL options. + + These options are available: + + * **driver**: The database driver to use, defaults to ``pdo_mysql``. + Can be any of: ``pdo_mysql``, ``pdo_sqlite``, ``pdo_pgsql``, + ``pdo_oci``, ``oci8``, ``ibm_db2``, ``pdo_ibm``, ``pdo_sqlsrv``. + + * **dbname**: The name of the database to connect to. + + * **host**: The host of the database to connect to. Defaults to + localhost. + + * **user**: The user of the database to connect to. Defaults to + root. + + * **password**: The password of the database to connect to. + + * **charset**: Only relevant for ``pdo_mysql``, and ``pdo_oci/oci8``, + specifies the charset used when connecting to the database. + + * **path**: Only relevant for ``pdo_sqlite``, specifies the path to + the SQLite database. + + * **port**: Only relevant for ``pdo_mysql``, ``pdo_pgsql``, and ``pdo_oci/oci8``, + specifies the port of the database to connect to. + + These and additional options are described in detail in the `Doctrine DBAL + configuration documentation `_. + +Services +-------- + +* **db**: The database connection, instance of + ``Doctrine\DBAL\Connection``. + +* **db.config**: Configuration object for Doctrine. Defaults to + an empty ``Doctrine\DBAL\Configuration``. + +* **db.event_manager**: Event Manager for Doctrine. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\DoctrineServiceProvider(), array( + 'db.options' => array( + 'driver' => 'pdo_sqlite', + 'path' => __DIR__.'/app.db', + ), + )); + +.. note:: + + Add the Doctrine DBAL as a dependency: + + .. code-block:: bash + + composer require "doctrine/dbal:~2.2" + +Usage +----- + +The Doctrine provider provides a ``db`` service. Here is a usage +example:: + + $app->get('/blog/{id}', function ($id) use ($app) { + $sql = "SELECT * FROM posts WHERE id = ?"; + $post = $app['db']->fetchAssoc($sql, array((int) $id)); + + return "

{$post['title']}

". + "

{$post['body']}

"; + }); + +Using multiple databases +------------------------ + +The Doctrine provider can allow access to multiple databases. In order to +configure the data sources, replace the **db.options** with **dbs.options**. +**dbs.options** is an array of configurations where keys are connection names +and values are options:: + + $app->register(new Silex\Provider\DoctrineServiceProvider(), array( + 'dbs.options' => array ( + 'mysql_read' => array( + 'driver' => 'pdo_mysql', + 'host' => 'mysql_read.someplace.tld', + 'dbname' => 'my_database', + 'user' => 'my_username', + 'password' => 'my_password', + 'charset' => 'utf8mb4', + ), + 'mysql_write' => array( + 'driver' => 'pdo_mysql', + 'host' => 'mysql_write.someplace.tld', + 'dbname' => 'my_database', + 'user' => 'my_username', + 'password' => 'my_password', + 'charset' => 'utf8mb4', + ), + ), + )); + +The first registered connection is the default and can simply be accessed as +you would if there was only one connection. Given the above configuration, +these two lines are equivalent:: + + $app['db']->fetchAll('SELECT * FROM table'); + + $app['dbs']['mysql_read']->fetchAll('SELECT * FROM table'); + +Using multiple connections:: + + $app->get('/blog/{id}', function ($id) use ($app) { + $sql = "SELECT * FROM posts WHERE id = ?"; + $post = $app['dbs']['mysql_read']->fetchAssoc($sql, array((int) $id)); + + $sql = "UPDATE posts SET value = ? WHERE id = ?"; + $app['dbs']['mysql_write']->executeUpdate($sql, array('newValue', (int) $id)); + + return "

{$post['title']}

". + "

{$post['body']}

"; + }); + +For more information, consult the `Doctrine DBAL documentation +`_. diff --git a/vendor/silex/silex/doc/providers/form.rst b/vendor/silex/silex/doc/providers/form.rst new file mode 100644 index 00000000..6818b858 --- /dev/null +++ b/vendor/silex/silex/doc/providers/form.rst @@ -0,0 +1,216 @@ +Form +==== + +The *FormServiceProvider* provides a service for building forms in +your application with the Symfony Form component. + +Parameters +---------- + +* none + +Services +-------- + +* **form.factory**: An instance of `FormFactory + `_, + that is used to build a form. + +Registering +----------- + +.. code-block:: php + + use Silex\Provider\FormServiceProvider; + + $app->register(new FormServiceProvider()); + +.. note:: + + If you don't want to create your own form layout, it's fine: a default one + will be used. But you will have to register the :doc:`translation provider + ` as the default form layout requires it:: + + $app->register(new Silex\Provider\TranslationServiceProvider(), array( + 'translator.domains' => array(), + )); + + If you want to use validation with forms, do not forget to register the + :doc:`Validator provider `. + +.. note:: + + Add the Symfony Form Component as a dependency: + + .. code-block:: bash + + composer require symfony/form + + If you are going to use the validation extension with forms, you must also + add a dependency to the ``symfony/validator`` and ``symfony/config`` + components: + + .. code-block:: bash + + composer require symfony/validator symfony/config + + If you want to use forms in your Twig templates, you can also install the + Symfony Twig Bridge. Make sure to install, if you didn't do that already, + the Translation component in order for the bridge to work: + + .. code-block:: bash + + composer require symfony/twig-bridge + +Usage +----- + +The FormServiceProvider provides a ``form.factory`` service. Here is a usage +example:: + + use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + use Symfony\Component\Form\Extension\Core\Type\FormType; + use Symfony\Component\Form\Extension\Core\Type\SubmitType; + + $app->match('/form', function (Request $request) use ($app) { + // some default data for when the form is displayed the first time + $data = array( + 'name' => 'Your name', + 'email' => 'Your email', + ); + + $form = $app['form.factory']->createBuilder(FormType::class, $data) + ->add('name') + ->add('email') + ->add('billing_plan', ChoiceType::class, array( + 'choices' => array('free' => 1, 'small business' => 2, 'corporate' => 3), + 'expanded' => true, + )) + ->add('submit', SubmitType::class, [ + 'label' => 'Save', + ]) + ->getForm(); + + $form->handleRequest($request); + + if ($form->isValid()) { + $data = $form->getData(); + + // do something with the data + + // redirect somewhere + return $app->redirect('...'); + } + + // display the form + return $app['twig']->render('index.twig', array('form' => $form->createView())); + }); + +And here is the ``index.twig`` form template (requires ``symfony/twig-bridge``): + +.. code-block:: jinja + +
+ {{ form_widget(form) }} + + +
+ +If you are using the validator provider, you can also add validation to your +form by adding constraints on the fields:: + + use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + use Symfony\Component\Form\Extension\Core\Type\FormType; + use Symfony\Component\Form\Extension\Core\Type\SubmitType; + use Symfony\Component\Form\Extension\Core\Type\TextType; + use Symfony\Component\Validator\Constraints as Assert; + + $app->register(new Silex\Provider\ValidatorServiceProvider()); + $app->register(new Silex\Provider\TranslationServiceProvider(), array( + 'translator.domains' => array(), + )); + + $form = $app['form.factory']->createBuilder(FormType::class) + ->add('name', TextType::class, array( + 'constraints' => array(new Assert\NotBlank(), new Assert\Length(array('min' => 5))) + )) + ->add('email', TextType::class, array( + 'constraints' => new Assert\Email() + )) + ->add('billing_plan', ChoiceType::class, array( + 'choices' => array('free' => 1, 'small business' => 2, 'corporate' => 3), + 'expanded' => true, + 'constraints' => new Assert\Choice(array(1, 2, 3)), + )) + ->add('submit', SubmitType::class, [ + 'label' => 'Save', + ]) + ->getForm(); + +You can register form types by extending ``form.types``:: + + $app['your.type.service'] = function ($app) { + return new YourServiceFormType(); + }; + $app->extend('form.types', function ($types) use ($app) { + $types[] = new YourFormType(); + $types[] = 'your.type.service'; + + return $types; + })); + +You can register form extensions by extending ``form.extensions``:: + + $app->extend('form.extensions', function ($extensions) use ($app) { + $extensions[] = new YourTopFormExtension(); + + return $extensions; + }); + + +You can register form type extensions by extending ``form.type.extensions``:: + + $app['your.type.extension.service'] = function ($app) { + return new YourServiceFormTypeExtension(); + }; + $app->extend('form.type.extensions', function ($extensions) use ($app) { + $extensions[] = new YourFormTypeExtension(); + $extensions[] = 'your.type.extension.service'; + + return $extensions; + }); + +You can register form type guessers by extending ``form.type.guessers``:: + + $app['your.type.guesser.service'] = function ($app) { + return new YourServiceFormTypeGuesser(); + }; + $app->extend('form.type.guessers', function ($guessers) use ($app) { + $guessers[] = new YourFormTypeGuesser(); + $guessers[] = 'your.type.guesser.service'; + + return $guessers; + }); + +.. warning:: + + CSRF protection is only available and automatically enabled when the + :doc:`CSRF Service Provider
` is registered. + +Traits +------ + +``Silex\Application\FormTrait`` adds the following shortcuts: + +* **form**: Creates a FormBuilderInterface instance. + +* **namedForm**: Creates a FormBuilderInterface instance (named). + +.. code-block:: php + + $app->form($data); + + $app->namedForm($name, $data, $options, $type); + +For more information, consult the `Symfony Forms documentation +`_. diff --git a/vendor/silex/silex/doc/providers/http_cache.rst b/vendor/silex/silex/doc/providers/http_cache.rst new file mode 100644 index 00000000..8bc98f67 --- /dev/null +++ b/vendor/silex/silex/doc/providers/http_cache.rst @@ -0,0 +1,128 @@ +HTTP Cache +========== + +The *HttpCacheServiceProvider* provides support for the Symfony Reverse +Proxy. + +Parameters +---------- + +* **http_cache.cache_dir**: The cache directory to store the HTTP cache data. + +* **http_cache.options** (optional): An array of options for the `HttpCache + `_ + constructor. + +Services +-------- + +* **http_cache**: An instance of `HttpCache + `_. + +* **http_cache.esi**: An instance of `Esi + `_, + that implements the ESI capabilities to Request and Response instances. + +* **http_cache.store**: An instance of `Store + `_, + that implements all the logic for storing cache metadata (Request and Response + headers). + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\HttpCacheServiceProvider(), array( + 'http_cache.cache_dir' => __DIR__.'/cache/', + )); + +Usage +----- + +Silex already supports any reverse proxy like Varnish out of the box by +setting Response HTTP cache headers:: + + use Symfony\Component\HttpFoundation\Response; + + $app->get('/', function() { + return new Response('Foo', 200, array( + 'Cache-Control' => 's-maxage=5', + )); + }); + +.. tip:: + + If you want Silex to trust the ``X-Forwarded-For*`` headers from your + reverse proxy at address $ip, you will need to whitelist it as documented + in `Trusting Proxies + `_. + + If you would be running Varnish in front of your application on the same machine:: + + use Symfony\Component\HttpFoundation\Request; + + Request::setTrustedProxies(array('127.0.0.1', '::1')); + $app->run(); + +This provider allows you to use the Symfony reverse proxy natively with +Silex applications by using the ``http_cache`` service. The Symfony reverse proxy +acts much like any other proxy would, so you will want to whitelist it:: + + use Symfony\Component\HttpFoundation\Request; + + Request::setTrustedProxies(array('127.0.0.1')); + $app['http_cache']->run(); + +The provider also provides ESI support:: + + $app->get('/', function() { + $response = new Response(<< + + Hello + + + + + EOF + , 200, array( + 'Surrogate-Control' => 'content="ESI/1.0"', + )); + + $response->setTtl(20); + + return $response; + }); + + $app->get('/included', function() { + $response = new Response('Foo'); + $response->setTtl(5); + + return $response; + }); + + $app['http_cache']->run(); + +If your application doesn't use ESI, you can disable it to slightly improve the +overall performance:: + + $app->register(new Silex\Provider\HttpCacheServiceProvider(), array( + 'http_cache.cache_dir' => __DIR__.'/cache/', + 'http_cache.esi' => null, + )); + +.. tip:: + + To help you debug caching issues, set your application ``debug`` to true. + Symfony automatically adds a ``X-Symfony-Cache`` header to each response + with useful information about cache hits and misses. + + If you are *not* using the Symfony Session provider, you might want to set + the PHP ``session.cache_limiter`` setting to an empty value to avoid the + default PHP behavior. + + Finally, check that your Web server does not override your caching strategy. + +For more information, consult the `Symfony HTTP Cache documentation +`_. diff --git a/vendor/silex/silex/doc/providers/http_fragment.rst b/vendor/silex/silex/doc/providers/http_fragment.rst new file mode 100644 index 00000000..8e681853 --- /dev/null +++ b/vendor/silex/silex/doc/providers/http_fragment.rst @@ -0,0 +1,70 @@ +HTTP Fragment +============= + +The *HttpFragmentServiceProvider* provides support for the Symfony fragment +sub-framework, which allows you to embed fragments of HTML in a template. + +Parameters +---------- + +* **fragment.path**: The path to use for the URL generated for ESI and + HInclude URLs (``/_fragment`` by default). + +* **uri_signer.secret**: The secret to use for the URI signer service (used + for the HInclude renderer). + +* **fragment.renderers.hinclude.global_template**: The content or Twig + template to use for the default content when using the HInclude renderer. + +Services +-------- + +* **fragment.handler**: An instance of `FragmentHandler + `_. + +* **fragment.renderers**: An array of fragment renderers (by default, the + inline, ESI, and HInclude renderers are pre-configured). + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\HttpFragmentServiceProvider()); + +Usage +----- + +.. note:: + + This section assumes that you are using Twig for your templates. + +Instead of building a page out of a single request/controller/template, the +fragment framework allows you to build a page from several +controllers/sub-requests/sub-templates by using **fragments**. + +Including "sub-pages" in the main page can be done with the Twig ``render()`` +function: + +.. code-block:: jinja + + The main page content. + + {{ render('/foo') }} + + The main page content resumes here. + +The ``render()`` call is replaced by the content of the ``/foo`` URL +(internally, a sub-request is handled by Silex to render the sub-page). + +Instead of making internal sub-requests, you can also use the ESI (the +sub-request is handled by a reverse proxy) or the HInclude strategies (the +sub-request is handled by a web browser): + +.. code-block:: jinja + + {{ render(url('route_name')) }} + + {{ render_esi(url('route_name')) }} + + {{ render_hinclude(url('route_name')) }} diff --git a/vendor/silex/silex/doc/providers/index.rst b/vendor/silex/silex/doc/providers/index.rst new file mode 100644 index 00000000..8c5a1754 --- /dev/null +++ b/vendor/silex/silex/doc/providers/index.rst @@ -0,0 +1,24 @@ +Built-in Service Providers +========================== + +.. toctree:: + :maxdepth: 1 + + twig + asset + monolog + session + swiftmailer + locale + translation + validator + form + csrf + http_cache + http_fragment + security + remember_me + serializer + service_controller + var_dumper + doctrine diff --git a/vendor/silex/silex/doc/providers/locale.rst b/vendor/silex/silex/doc/providers/locale.rst new file mode 100644 index 00000000..8f6cd675 --- /dev/null +++ b/vendor/silex/silex/doc/providers/locale.rst @@ -0,0 +1,24 @@ +Locale +====== + +The *LocaleServiceProvider* manages the locale of an application. + +Parameters +---------- + +* **locale**: The locale of the user. When set before any request handling, it + defines the default locale (``en`` by default). When a request is being + handled, it is automatically set according to the ``_locale`` request + attribute of the current route. + +Services +-------- + +* n/a + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\LocaleServiceProvider()); diff --git a/vendor/silex/silex/doc/providers/monolog.rst b/vendor/silex/silex/doc/providers/monolog.rst new file mode 100644 index 00000000..645b7106 --- /dev/null +++ b/vendor/silex/silex/doc/providers/monolog.rst @@ -0,0 +1,115 @@ +Monolog +======= + +The *MonologServiceProvider* provides a default logging mechanism through +Jordi Boggiano's `Monolog `_ library. + +It will log requests and errors and allow you to add logging to your +application. This allows you to debug and monitor the behaviour, +even in production. + +Parameters +---------- + +* **monolog.logfile**: File where logs are written to. +* **monolog.bubble**: (optional) Whether the messages that are handled can bubble up the stack or not. +* **monolog.permission**: (optional) File permissions default (null), nothing change. + +* **monolog.level** (optional): Level of logging, defaults + to ``DEBUG``. Must be one of ``Logger::DEBUG``, ``Logger::INFO``, + ``Logger::WARNING``, ``Logger::ERROR``. ``DEBUG`` will log + everything, ``INFO`` will log everything except ``DEBUG``, + etc. + + In addition to the ``Logger::`` constants, it is also possible to supply the + level in string form, for example: ``"DEBUG"``, ``"INFO"``, ``"WARNING"``, + ``"ERROR"``. + +* **monolog.name** (optional): Name of the monolog channel, + defaults to ``myapp``. + +* **monolog.exception.logger_filter** (optional): An anonymous function that + returns an error level for on uncaught exception that should be logged. + +* **monolog.use_error_handler** (optional): Whether errors and uncaught exceptions + should be handled by the Monolog ``ErrorHandler`` class and added to the log. + By default the error handler is enabled unless the application ``debug`` parameter + is set to true. + + Please note that enabling the error handler may silence some errors, + ignoring the PHP ``display_errors`` configuration setting. + +Services +-------- + +* **monolog**: The monolog logger instance. + + Example usage:: + + $app['monolog']->debug('Testing the Monolog logging.'); + +* **monolog.listener**: An event listener to log requests, responses and errors. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\MonologServiceProvider(), array( + 'monolog.logfile' => __DIR__.'/development.log', + )); + +.. note:: + + Add Monolog as a dependency: + + .. code-block:: bash + + composer require monolog/monolog + +Usage +----- + +The MonologServiceProvider provides a ``monolog`` service. You can use it to +add log entries for any logging level through ``debug()``, ``info()``, +``warning()`` and ``error()``:: + + use Symfony\Component\HttpFoundation\Response; + + $app->post('/user', function () use ($app) { + // ... + + $app['monolog']->info(sprintf("User '%s' registered.", $username)); + + return new Response('', 201); + }); + +Customization +------------- + +You can configure Monolog (like adding or changing the handlers) before using +it by extending the ``monolog`` service:: + + $app->extend('monolog', function($monolog, $app) { + $monolog->pushHandler(...); + + return $monolog; + }); + +By default, all requests, responses and errors are logged by an event listener +registered as a service called `monolog.listener`. You can replace or remove +this service if you want to modify or disable the logged information. + +Traits +------ + +``Silex\Application\MonologTrait`` adds the following shortcuts: + +* **log**: Logs a message. + +.. code-block:: php + + $app->log(sprintf("User '%s' registered.", $username)); + +For more information, check out the `Monolog documentation +`_. diff --git a/vendor/silex/silex/doc/providers/remember_me.rst b/vendor/silex/silex/doc/providers/remember_me.rst new file mode 100644 index 00000000..7fdaaaba --- /dev/null +++ b/vendor/silex/silex/doc/providers/remember_me.rst @@ -0,0 +1,69 @@ +Remember Me +=========== + +The *RememberMeServiceProvider* adds "Remember-Me" authentication to the +*SecurityServiceProvider*. + +Parameters +---------- + +n/a + +Services +-------- + +n/a + +.. note:: + + The service provider defines many other services that are used internally + but rarely need to be customized. + +Registering +----------- + +Before registering this service provider, you must register the +*SecurityServiceProvider*:: + + $app->register(new Silex\Provider\SecurityServiceProvider()); + $app->register(new Silex\Provider\RememberMeServiceProvider()); + + $app['security.firewalls'] = array( + 'my-firewall' => array( + 'pattern' => '^/secure$', + 'form' => true, + 'logout' => true, + 'remember_me' => array( + 'key' => 'Choose_A_Unique_Random_Key', + 'always_remember_me' => true, + /* Other options */ + ), + 'users' => array( /* ... */ ), + ), + ); + +Options +------- + +* **key**: A secret key to generate tokens (you should generate a random + string). + +* **name**: Cookie name (default: ``REMEMBERME``). + +* **lifetime**: Cookie lifetime (default: ``31536000`` ~ 1 year). + +* **path**: Cookie path (default: ``/``). + +* **domain**: Cookie domain (default: ``null`` = request domain). + +* **secure**: Cookie is secure (default: ``false``). + +* **httponly**: Cookie is HTTP only (default: ``true``). + +* **always_remember_me**: Enable remember me (default: ``false``). + +* **remember_me_parameter**: Name of the request parameter enabling remember_me + on login. To add the checkbox to the login form. You can find more + information in the `Symfony cookbook + `_ + (default: ``_remember_me``). diff --git a/vendor/silex/silex/doc/providers/security.rst b/vendor/silex/silex/doc/providers/security.rst new file mode 100644 index 00000000..f84d3180 --- /dev/null +++ b/vendor/silex/silex/doc/providers/security.rst @@ -0,0 +1,711 @@ +Security +======== + +The *SecurityServiceProvider* manages authentication and authorization for +your applications. + +Parameters +---------- + +* **security.hide_user_not_found** (optional): Defines whether to hide user not + found exception or not. Defaults to ``true``. + +* **security.encoder.bcrypt.cost** (optional): Defines BCrypt password encoder cost. Defaults to 13. + +Services +-------- + +* **security.token_storage**: Gives access to the user token. + +* **security.authorization_checker**: Allows to check authorizations for the + users. + +* **security.authentication_manager**: An instance of + `AuthenticationProviderManager + `_, + responsible for authentication. + +* **security.access_manager**: An instance of `AccessDecisionManager + `_, + responsible for authorization. + +* **security.session_strategy**: Define the session strategy used for + authentication (default to a migration strategy). + +* **security.user_checker**: Checks user flags after authentication. + +* **security.last_error**: Returns the last authentication errors when given a + Request object. + +* **security.encoder_factory**: Defines the encoding strategies for user + passwords (uses ``security.default_encoder``). + +* **security.default_encoder**: The encoder to use by default for all users (BCrypt). + +* **security.encoder.digest**: Digest password encoder. + +* **security.encoder.bcrypt**: BCrypt password encoder. + +* **security.encoder.pbkdf2**: Pbkdf2 password encoder. + +* **user**: Returns the current user + +.. note:: + + The service provider defines many other services that are used internally + but rarely need to be customized. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\SecurityServiceProvider(), array( + 'security.firewalls' => // see below + )); + +.. note:: + + Add the Symfony Security Component as a dependency: + + .. code-block:: bash + + composer require symfony/security + +.. caution:: + + If you're using a form to authenticate users, you need to enable + ``SessionServiceProvider``. + +.. caution:: + + The security features are only available after the Application has been + booted. So, if you want to use it outside of the handling of a request, + don't forget to call ``boot()`` first:: + + $app->boot(); + +Usage +----- + +The Symfony Security component is powerful. To learn more about it, read the +`Symfony Security documentation +`_. + +.. tip:: + + When a security configuration does not behave as expected, enable logging + (with the Monolog extension for instance) as the Security Component logs a + lot of interesting information about what it does and why. + +Below is a list of recipes that cover some common use cases. + +Accessing the current User +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The current user information is stored in a token that is accessible via the +``security`` service:: + + $token = $app['security.token_storage']->getToken(); + +If there is no information about the user, the token is ``null``. If the user +is known, you can get it with a call to ``getUser()``:: + + if (null !== $token) { + $user = $token->getUser(); + } + +The user can be a string, an object with a ``__toString()`` method, or an +instance of `UserInterface +`_. + +Securing a Path with HTTP Authentication +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following configuration uses HTTP basic authentication to secure URLs +under ``/admin/``:: + + $app['security.firewalls'] = array( + 'admin' => array( + 'pattern' => '^/admin', + 'http' => true, + 'users' => array( + // raw password is foo + 'admin' => array('ROLE_ADMIN', '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a'), + ), + ), + ); + +The ``pattern`` is a regular expression on the URL path; the ``http`` setting +tells the security layer to use HTTP basic authentication and the ``users`` +entry defines valid users. + +If you want to restrict the firewall by more than the URL pattern (like the +HTTP method, the client IP, the hostname, or any Request attributes), use an +instance of a `RequestMatcher +`_ +for the ``pattern`` option:: + + use Symfony/Component/HttpFoundation/RequestMatcher; + + $app['security.firewalls'] = array( + 'admin' => array( + 'pattern' => new RequestMatcher('^/admin', 'example.com', 'POST'), + // ... + ), + ); + +Each user is defined with the following information: + +* The role or an array of roles for the user (roles are strings beginning with + ``ROLE_`` and ending with anything you want); + +* The user encoded password. + +.. caution:: + + All users must at least have one role associated with them. + +The default configuration of the extension enforces encoded passwords. To +generate a valid encoded password from a raw password, use the +``security.encoder_factory`` service:: + + // find the encoder for a UserInterface instance + $encoder = $app['security.encoder_factory']->getEncoder($user); + + // compute the encoded password for foo + $password = $encoder->encodePassword('foo', $user->getSalt()); + +When the user is authenticated, the user stored in the token is an instance of +`User +`_ + +.. caution:: + + If you are using php-cgi under Apache, you need to add this configuration + to make things work correctly: + + .. code-block:: apache + + RewriteEngine On + RewriteCond %{HTTP:Authorization} ^(.+)$ + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ app.php [QSA,L] + +Securing a Path with a Form +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Using a form to authenticate users is very similar to the above configuration. +Instead of using the ``http`` setting, use the ``form`` one and define these +two parameters: + +* **login_path**: The login path where the user is redirected when they are + accessing a secured area without being authenticated so that they can enter + their credentials; + +* **check_path**: The check URL used by Symfony to validate the credentials of + the user. + +Here is how to secure all URLs under ``/admin/`` with a form:: + + $app['security.firewalls'] = array( + 'admin' => array( + 'pattern' => '^/admin/', + 'form' => array('login_path' => '/login', 'check_path' => '/admin/login_check'), + 'users' => array( + 'admin' => array('ROLE_ADMIN', '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a'), + ), + ), + ); + +Always keep in mind the following two golden rules: + +* The ``login_path`` path must always be defined **outside** the secured area + (or if it is in the secured area, the ``anonymous`` authentication mechanism + must be enabled -- see below); + +* The ``check_path`` path must always be defined **inside** the secured area. + +For the login form to work, create a controller like the following:: + + use Symfony\Component\HttpFoundation\Request; + + $app->get('/login', function(Request $request) use ($app) { + return $app['twig']->render('login.html', array( + 'error' => $app['security.last_error']($request), + 'last_username' => $app['session']->get('_security.last_username'), + )); + }); + +The ``error`` and ``last_username`` variables contain the last authentication +error and the last username entered by the user in case of an authentication +error. + +Create the associated template: + +.. code-block:: jinja + +
+ {{ error }} + + + +
+ +.. note:: + + The ``admin_login_check`` route is automatically defined by Silex and its + name is derived from the ``check_path`` value (all ``/`` are replaced with + ``_`` and the leading ``/`` is stripped). + +Defining more than one Firewall +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You are not limited to define one firewall per project. + +Configuring several firewalls is useful when you want to secure different +parts of your website with different authentication strategies or for +different users (like using an HTTP basic authentication for the website API +and a form to secure your website administration area). + +It's also useful when you want to secure all URLs except the login form:: + + $app['security.firewalls'] = array( + 'login' => array( + 'pattern' => '^/login$', + ), + 'secured' => array( + 'pattern' => '^.*$', + 'form' => array('login_path' => '/login', 'check_path' => '/login_check'), + 'users' => array( + 'admin' => array('ROLE_ADMIN', '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a'), + ), + ), + ); + +The order of the firewall configurations is significant as the first one to +match wins. The above configuration first ensures that the ``/login`` URL is +not secured (no authentication settings), and then it secures all other URLs. + +.. tip:: + + You can toggle all registered authentication mechanisms for a particular + area on and off with the ``security`` flag:: + + $app['security.firewalls'] = array( + 'api' => array( + 'pattern' => '^/api', + 'security' => $app['debug'] ? false : true, + 'wsse' => true, + + // ... + ), + ); + +Adding a Logout +~~~~~~~~~~~~~~~ + +When using a form for authentication, you can let users log out if you add the +``logout`` setting, where ``logout_path`` must match the main firewall +pattern:: + + $app['security.firewalls'] = array( + 'secured' => array( + 'pattern' => '^/admin/', + 'form' => array('login_path' => '/login', 'check_path' => '/admin/login_check'), + 'logout' => array('logout_path' => '/admin/logout', 'invalidate_session' => true), + + // ... + ), + ); + +A route is automatically generated, based on the configured path (all ``/`` +are replaced with ``_`` and the leading ``/`` is stripped): + +.. code-block:: jinja + + Logout + +Allowing Anonymous Users +~~~~~~~~~~~~~~~~~~~~~~~~ + +When securing only some parts of your website, the user information are not +available in non-secured areas. To make the user accessible in such areas, +enabled the ``anonymous`` authentication mechanism:: + + $app['security.firewalls'] = array( + 'unsecured' => array( + 'anonymous' => true, + + // ... + ), + ); + +When enabling the anonymous setting, a user will always be accessible from the +security context; if the user is not authenticated, it returns the ``anon.`` +string. + +Checking User Roles +~~~~~~~~~~~~~~~~~~~ + +To check if a user is granted some role, use the ``isGranted()`` method on the +security context:: + + if ($app['security.authorization_checker']->isGranted('ROLE_ADMIN')) { + // ... + } + +You can check roles in Twig templates too: + +.. code-block:: jinja + + {% if is_granted('ROLE_ADMIN') %} + Switch to Fabien + {% endif %} + +You can check if a user is "fully authenticated" (not an anonymous user for +instance) with the special ``IS_AUTHENTICATED_FULLY`` role: + +.. code-block:: jinja + + {% if is_granted('IS_AUTHENTICATED_FULLY') %} + Logout + {% else %} + Login + {% endif %} + +Of course you will need to define a ``login`` route for this to work. + +.. tip:: + + Don't use the ``getRoles()`` method to check user roles. + +.. caution:: + + ``isGranted()`` throws an exception when no authentication information is + available (which is the case on non-secured area). + +Impersonating a User +~~~~~~~~~~~~~~~~~~~~ + +If you want to be able to switch to another user (without knowing the user +credentials), enable the ``switch_user`` authentication strategy:: + + $app['security.firewalls'] = array( + 'unsecured' => array( + 'switch_user' => array('parameter' => '_switch_user', 'role' => 'ROLE_ALLOWED_TO_SWITCH'), + + // ... + ), + ); + +Switching to another user is now a matter of adding the ``_switch_user`` query +parameter to any URL when logged in as a user who has the +``ROLE_ALLOWED_TO_SWITCH`` role: + +.. code-block:: jinja + + {% if is_granted('ROLE_ALLOWED_TO_SWITCH') %} + Switch to user Fabien + {% endif %} + +You can check that you are impersonating a user by checking the special +``ROLE_PREVIOUS_ADMIN``. This is useful for instance to allow the user to +switch back to their primary account: + +.. code-block:: jinja + + {% if is_granted('ROLE_PREVIOUS_ADMIN') %} + You are an admin but you've switched to another user, + exit the switch. + {% endif %} + +Defining a Role Hierarchy +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Defining a role hierarchy allows to automatically grant users some additional +roles:: + + $app['security.role_hierarchy'] = array( + 'ROLE_ADMIN' => array('ROLE_USER', 'ROLE_ALLOWED_TO_SWITCH'), + ); + +With this configuration, all users with the ``ROLE_ADMIN`` role also +automatically have the ``ROLE_USER`` and ``ROLE_ALLOWED_TO_SWITCH`` roles. + +Defining Access Rules +~~~~~~~~~~~~~~~~~~~~~ + +Roles are a great way to adapt the behavior of your website depending on +groups of users, but they can also be used to further secure some areas by +defining access rules:: + + $app['security.access_rules'] = array( + array('^/admin', 'ROLE_ADMIN', 'https'), + array('^.*$', 'ROLE_USER'), + ); + +With the above configuration, users must have the ``ROLE_ADMIN`` to access the +``/admin`` section of the website, and ``ROLE_USER`` for everything else. +Furthermore, the admin section can only be accessible via HTTPS (if that's not +the case, the user will be automatically redirected). + +.. note:: + + The first argument can also be a `RequestMatcher + `_ + instance. + +Defining a custom User Provider +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Using an array of users is simple and useful when securing an admin section of +a personal website, but you can override this default mechanism with you own. + +The ``users`` setting can be defined as a service that returns an instance of +`UserProviderInterface +`_:: + + 'users' => function () use ($app) { + return new UserProvider($app['db']); + }, + +Here is a simple example of a user provider, where Doctrine DBAL is used to +store the users:: + + use Symfony\Component\Security\Core\User\UserProviderInterface; + use Symfony\Component\Security\Core\User\UserInterface; + use Symfony\Component\Security\Core\User\User; + use Symfony\Component\Security\Core\Exception\UnsupportedUserException; + use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; + use Doctrine\DBAL\Connection; + + class UserProvider implements UserProviderInterface + { + private $conn; + + public function __construct(Connection $conn) + { + $this->conn = $conn; + } + + public function loadUserByUsername($username) + { + $stmt = $this->conn->executeQuery('SELECT * FROM users WHERE username = ?', array(strtolower($username))); + + if (!$user = $stmt->fetch()) { + throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username)); + } + + return new User($user['username'], $user['password'], explode(',', $user['roles']), true, true, true, true); + } + + public function refreshUser(UserInterface $user) + { + if (!$user instanceof User) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); + } + + return $this->loadUserByUsername($user->getUsername()); + } + + public function supportsClass($class) + { + return $class === 'Symfony\Component\Security\Core\User\User'; + } + } + +In this example, instances of the default ``User`` class are created for the +users, but you can define your own class; the only requirement is that the +class must implement `UserInterface +`_ + +And here is the code that you can use to create the database schema and some +sample users:: + + use Doctrine\DBAL\Schema\Table; + + $schema = $app['db']->getSchemaManager(); + if (!$schema->tablesExist('users')) { + $users = new Table('users'); + $users->addColumn('id', 'integer', array('unsigned' => true, 'autoincrement' => true)); + $users->setPrimaryKey(array('id')); + $users->addColumn('username', 'string', array('length' => 32)); + $users->addUniqueIndex(array('username')); + $users->addColumn('password', 'string', array('length' => 255)); + $users->addColumn('roles', 'string', array('length' => 255)); + + $schema->createTable($users); + + $app['db']->insert('users', array( + 'username' => 'fabien', + 'password' => '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a', + 'roles' => 'ROLE_USER' + )); + + $app['db']->insert('users', array( + 'username' => 'admin', + 'password' => '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a', + 'roles' => 'ROLE_ADMIN' + )); + } + +.. tip:: + + If you are using the Doctrine ORM, the Symfony bridge for Doctrine + provides a user provider class that is able to load users from your + entities. + +Defining a custom Encoder +~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, Silex uses the ``BCrypt`` algorithm to encode passwords. +Additionally, the password is encoded multiple times. +You can change these defaults by overriding ``security.default_encoder`` +service to return one of the predefined encoders: + +* **security.encoder.digest**: Digest password encoder. + +* **security.encoder.bcrypt**: BCrypt password encoder. + +* **security.encoder.pbkdf2**: Pbkdf2 password encoder. + +.. code-block:: php + + $app['security.default_encoder'] = function ($app) { + return $app['security.encoder.pbkdf2']; + }; + +Or you can define you own, fully customizable encoder:: + + use Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder; + + $app['security.default_encoder'] = function ($app) { + // Plain text (e.g. for debugging) + return new PlaintextPasswordEncoder(); + }; + +.. tip:: + + You can change the default BCrypt encoding cost by overriding ``security.encoder.bcrypt.cost`` + +Defining a custom Authentication Provider +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Symfony Security component provides a lot of ready-to-use authentication +providers (form, HTTP, X509, remember me, ...), but you can add new ones +easily. To register a new authentication provider, create a service named +``security.authentication_listener.factory.XXX`` where ``XXX`` is the name you want to +use in your configuration:: + + $app['security.authentication_listener.factory.wsse'] = $app->protect(function ($name, $options) use ($app) { + // define the authentication provider object + $app['security.authentication_provider.'.$name.'.wsse'] = function () use ($app) { + return new WsseProvider($app['security.user_provider.default'], __DIR__.'/security_cache'); + }; + + // define the authentication listener object + $app['security.authentication_listener.'.$name.'.wsse'] = function () use ($app) { + return new WsseListener($app['security.token_storage'], $app['security.authentication_manager']); + }; + + return array( + // the authentication provider id + 'security.authentication_provider.'.$name.'.wsse', + // the authentication listener id + 'security.authentication_listener.'.$name.'.wsse', + // the entry point id + null, + // the position of the listener in the stack + 'pre_auth' + ); + }); + +You can now use it in your configuration like any other built-in +authentication provider:: + + $app->register(new Silex\Provider\SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'wsse' => true, + + // ... + ), + ), + )); + +Instead of ``true``, you can also define an array of options that customize +the behavior of your authentication factory; it will be passed as the second +argument of your authentication factory (see above). + +This example uses the authentication provider classes as described in the +Symfony `cookbook`_. + + +.. note:: + + The Guard component simplifies the creation of custom authentication + providers. :doc:`How to Create a Custom Authentication System with Guard + ` + +Stateless Authentication +~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, a session cookie is created to persist the security context of +the user. However, if you use certificates, HTTP authentication, WSSE and so +on, the credentials are sent for each request. In that case, you can turn off +persistence by activating the ``stateless`` authentication flag:: + + $app['security.firewalls'] = array( + 'default' => array( + 'stateless' => true, + 'wsse' => true, + + // ... + ), + ); + +Traits +------ + +``Silex\Application\SecurityTrait`` adds the following shortcuts: + +* **encodePassword**: Encode a given password. + +.. code-block:: php + + $user = $app->user(); + + $encoded = $app->encodePassword($user, 'foo'); + +``Silex\Route\SecurityTrait`` adds the following methods to the controllers: + +* **secure**: Secures a controller for the given roles. + +.. code-block:: php + + $app->get('/', function () { + // do something but only for admins + })->secure('ROLE_ADMIN'); + +.. caution:: + + The ``Silex\Route\SecurityTrait`` must be used with a user defined + ``Route`` class, not the application. + + .. code-block:: php + + use Silex\Route; + + class MyRoute extends Route + { + use Route\SecurityTrait; + } + + .. code-block:: php + + $app['route_class'] = 'MyRoute'; + + +.. _cookbook: http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html diff --git a/vendor/silex/silex/doc/providers/serializer.rst b/vendor/silex/silex/doc/providers/serializer.rst new file mode 100644 index 00000000..162dbab6 --- /dev/null +++ b/vendor/silex/silex/doc/providers/serializer.rst @@ -0,0 +1,73 @@ +Serializer +========== + +The *SerializerServiceProvider* provides a service for serializing objects. + +Parameters +---------- + +None. + +Services +-------- + +* **serializer**: An instance of `Symfony\\Component\\Serializer\\Serializer + `_. + +* **serializer.encoders**: `Symfony\\Component\\Serializer\\Encoder\\JsonEncoder + `_ + and `Symfony\\Component\\Serializer\\Encoder\\XmlEncoder + `_. + +* **serializer.normalizers**: `Symfony\\Component\\Serializer\\Normalizer\\CustomNormalizer + `_ + and `Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer + `_. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\SerializerServiceProvider()); + +.. note:: + + Add the Symfony's `Serializer Component + `_ as a + dependency: + + .. code-block:: bash + + composer require symfony/serializer + +Usage +----- + +The ``SerializerServiceProvider`` provider provides a ``serializer`` service:: + + use Silex\Application; + use Silex\Provider\SerializerServiceProvider; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + $app = new Application(); + + $app->register(new SerializerServiceProvider()); + + // only accept content types supported by the serializer via the assert method. + $app->get("/pages/{id}.{_format}", function (Request $request, $id) use ($app) { + // assume a page_repository service exists that returns Page objects. The + // object returned has getters and setters exposing the state. + $page = $app['page_repository']->find($id); + $format = $request->getRequestFormat(); + + if (!$page instanceof Page) { + $app->abort("No page found for id: $id"); + } + + return new Response($app['serializer']->serialize($page, $format), 200, array( + "Content-Type" => $request->getMimeType($format) + )); + })->assert("_format", "xml|json") + ->assert("id", "\d+"); diff --git a/vendor/silex/silex/doc/providers/service_controller.rst b/vendor/silex/silex/doc/providers/service_controller.rst new file mode 100644 index 00000000..15bca28d --- /dev/null +++ b/vendor/silex/silex/doc/providers/service_controller.rst @@ -0,0 +1,142 @@ +Service Controllers +=================== + +As your Silex application grows, you may wish to begin organizing your +controllers in a more formal fashion. Silex can use controller classes out of +the box, but with a bit of work, your controllers can be created as services, +giving you the full power of dependency injection and lazy loading. + +.. ::todo Link above to controller classes cookbook + +Why would I want to do this? +---------------------------- + +- Dependency Injection over Service Location + + Using this method, you can inject the actual dependencies required by your + controller and gain total inversion of control, while still maintaining the + lazy loading of your controllers and its dependencies. Because your + dependencies are clearly defined, they are easily mocked, allowing you to test + your controllers in isolation. + +- Framework Independence + + Using this method, your controllers start to become more independent of the + framework you are using. Carefully crafted, your controllers will become + reusable with multiple frameworks. By keeping careful control of your + dependencies, your controllers could easily become compatible with Silex, + Symfony (full stack) and Drupal, to name just a few. + +Parameters +---------- + +There are currently no parameters for the ``ServiceControllerServiceProvider``. + +Services +-------- + +There are no extra services provided, the ``ServiceControllerServiceProvider`` +simply extends the existing **resolver** service. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\ServiceControllerServiceProvider()); + +Usage +----- + +In this slightly contrived example of a blog API, we're going to change the +``/posts.json`` route to use a controller, that is defined as a service. + +.. code-block:: php + + use Silex\Application; + use Demo\Repository\PostRepository; + + $app = new Application(); + + $app['posts.repository'] = function() { + return new PostRepository; + }; + + $app->get('/posts.json', function() use ($app) { + return $app->json($app['posts.repository']->findAll()); + }); + +Rewriting your controller as a service is pretty simple, create a Plain Ol' PHP +Object with your ``PostRepository`` as a dependency, along with an +``indexJsonAction`` method to handle the request. Although not shown in the +example below, you can use type hinting and parameter naming to get the +parameters you need, just like with standard Silex routes. + +If you are a TDD/BDD fan (and you should be), you may notice that this +controller has well defined responsibilities and dependencies, and is easily +tested/specced. You may also notice that the only external dependency is on +``Symfony\Component\HttpFoundation\JsonResponse``, meaning this controller could +easily be used in a Symfony (full stack) application, or potentially with other +applications or frameworks that know how to handle a `Symfony/HttpFoundation +`_ +``Response`` object. + +.. code-block:: php + + namespace Demo\Controller; + + use Demo\Repository\PostRepository; + use Symfony\Component\HttpFoundation\JsonResponse; + + class PostController + { + protected $repo; + + public function __construct(PostRepository $repo) + { + $this->repo = $repo; + } + + public function indexJsonAction() + { + return new JsonResponse($this->repo->findAll()); + } + } + +And lastly, define your controller as a service in the application, along with +your route. The syntax in the route definition is the name of the service, +followed by a single colon (:), followed by the method name. + +.. code-block:: php + + $app['posts.controller'] = function() use ($app) { + return new PostController($app['posts.repository']); + }; + + $app->get('/posts.json', "posts.controller:indexJsonAction"); + +In addition to using classes for service controllers, you can define any +callable as a service in the application to be used for a route. + +.. code-block:: php + + namespace Demo\Controller; + + use Demo\Repository\PostRepository; + use Symfony\Component\HttpFoundation\JsonResponse; + + function postIndexJson(PostRepository $repo) { + return function() use ($repo) { + return new JsonResponse($repo->findAll()); + }; + } + +And when defining your route, the code would look like the following: + +.. code-block:: php + + $app['posts.controller'] = function($app) { + return Demo\Controller\postIndexJson($app['posts.repository']); + }; + + $app->get('/posts.json', 'posts.controller'); diff --git a/vendor/silex/silex/doc/providers/session.rst b/vendor/silex/silex/doc/providers/session.rst new file mode 100644 index 00000000..011b69fe --- /dev/null +++ b/vendor/silex/silex/doc/providers/session.rst @@ -0,0 +1,120 @@ +Session +======= + +The *SessionServiceProvider* provides a service for storing data persistently +between requests. + +Parameters +---------- + +* **session.storage.save_path** (optional): The path for the + ``NativeFileSessionHandler``, defaults to the value of + ``sys_get_temp_dir()``. + +* **session.storage.options**: An array of options that is passed to the + constructor of the ``session.storage`` service. + + In case of the default `NativeSessionStorage + `_, + the most useful options are: + + * **name**: The cookie name (_SESS by default) + * **id**: The session id (null by default) + * **cookie_lifetime**: Cookie lifetime + * **cookie_path**: Cookie path + * **cookie_domain**: Cookie domain + * **cookie_secure**: Cookie secure (HTTPS) + * **cookie_httponly**: Whether the cookie is http only + + However, all of these are optional. Default Sessions life time is 1800 + seconds (30 minutes). To override this, set the ``lifetime`` option. + + For a full list of available options, read the `PHP + `_ official documentation. + +* **session.test**: Whether to simulate sessions or not (useful when writing + functional tests). + +Services +-------- + +* **session**: An instance of Symfony's `Session + `_. + +* **session.storage**: A service that is used for persistence of the session + data. + +* **session.storage.handler**: A service that is used by the + ``session.storage`` for data access. Defaults to a `NativeFileSessionHandler + `_ + storage handler. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\SessionServiceProvider()); + +Using Handlers +-------------- + +The default session handler is ``NativeFileSessionHandler``. However, there are +multiple handlers available for use by setting ``session.storage.handler`` to +an instance of one of the following handler objects: + +* `LegacyPdoSessionHandler `_ +* `MemcacheSessionHandler `_ +* `MemcachedSessionHandler `_ +* `MongoDbSessionHandler `_ +* `NativeFileSessionHandler `_ +* `NativeSessionHandler `_ +* `NullSessionHandler `_ +* `PdoSessionHandler `_ +* `WriteCheckSessionHandler `_ + +Usage +----- + +The Session provider provides a ``session`` service. Here is an example that +authenticates a user and creates a session for them:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + $app->get('/login', function (Request $request) use ($app) { + $username = $request->server->get('PHP_AUTH_USER', false); + $password = $request->server->get('PHP_AUTH_PW'); + + if ('igor' === $username && 'password' === $password) { + $app['session']->set('user', array('username' => $username)); + return $app->redirect('/account'); + } + + $response = new Response(); + $response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', 'site_login')); + $response->setStatusCode(401, 'Please sign in.'); + return $response; + }); + + $app->get('/account', function () use ($app) { + if (null === $user = $app['session']->get('user')) { + return $app->redirect('/login'); + } + + return "Welcome {$user['username']}!"; + }); + + +Custom Session Configurations +----------------------------- + +If your system is using a custom session configuration (such as a redis handler +from a PHP extension) then you need to disable the NativeFileSessionHandler by +setting ``session.storage.handler`` to null. You will have to configure the +``session.save_path`` ini setting yourself in that case. + +.. code-block:: php + + $app['session.storage.handler'] = null; + diff --git a/vendor/silex/silex/doc/providers/swiftmailer.rst b/vendor/silex/silex/doc/providers/swiftmailer.rst new file mode 100644 index 00000000..9297d665 --- /dev/null +++ b/vendor/silex/silex/doc/providers/swiftmailer.rst @@ -0,0 +1,156 @@ +Swiftmailer +=========== + +The *SwiftmailerServiceProvider* provides a service for sending email through +the `Swift Mailer `_ library. + +You can use the ``mailer`` service to send messages easily. By default, it +will attempt to send emails through SMTP. + +Parameters +---------- + +* **swiftmailer.use_spool**: A boolean to specify whether or not to use the + memory spool, defaults to true. + +* **swiftmailer.options**: An array of options for the default SMTP-based + configuration. + + The following options can be set: + + * **host**: SMTP hostname, defaults to 'localhost'. + * **port**: SMTP port, defaults to 25. + * **username**: SMTP username, defaults to an empty string. + * **password**: SMTP password, defaults to an empty string. + * **encryption**: SMTP encryption, defaults to null. Valid values are 'tls', 'ssl', or null (indicating no encryption). + * **auth_mode**: SMTP authentication mode, defaults to null. Valid values are 'plain', 'login', 'cram-md5', or null. + + Example usage:: + + $app['swiftmailer.options'] = array( + 'host' => 'host', + 'port' => '25', + 'username' => 'username', + 'password' => 'password', + 'encryption' => null, + 'auth_mode' => null + ); + +* **swiftmailer.sender_address**: If set, all messages will be delivered with + this address as the "return path" address. + +* **swiftmailer.delivery_addresses**: If not empty, all email messages will be + sent to those addresses instead of being sent to their actual recipients. This + is often useful when developing. + +* **swiftmailer.delivery_whitelist**: Used in combination with + ``delivery_addresses``. If set, emails matching any of these patterns will be + delivered like normal, as well as being sent to ``delivery_addresses``. + +* **swiftmailer.plugins**: Array of SwiftMailer plugins. + + Example usage:: + + $app['swiftmailer.plugins'] = function ($app) { + return array( + new \Swift_Plugins_PopBeforeSmtpPlugin('pop3.example.com'), + ); + }; + +Services +-------- + +* **mailer**: The mailer instance. + + Example usage:: + + $message = \Swift_Message::newInstance(); + + // ... + + $app['mailer']->send($message); + +* **swiftmailer.transport**: The transport used for e-mail + delivery. Defaults to a ``Swift_Transport_EsmtpTransport``. + +* **swiftmailer.transport.buffer**: StreamBuffer used by + the transport. + +* **swiftmailer.transport.authhandler**: Authentication + handler used by the transport. Will try the following + by default: CRAM-MD5, login, plaintext. + +* **swiftmailer.transport.eventdispatcher**: Internal event + dispatcher used by Swiftmailer. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\SwiftmailerServiceProvider()); + +.. note:: + + Add SwiftMailer as a dependency: + + .. code-block:: bash + + composer require swiftmailer/swiftmailer + +Usage +----- + +The Swiftmailer provider provides a ``mailer`` service:: + + use Symfony\Component\HttpFoundation\Request; + + $app->post('/feedback', function (Request $request) use ($app) { + $message = \Swift_Message::newInstance() + ->setSubject('[YourSite] Feedback') + ->setFrom(array('noreply@yoursite.com')) + ->setTo(array('feedback@yoursite.com')) + ->setBody($request->get('message')); + + $app['mailer']->send($message); + + return new Response('Thank you for your feedback!', 201); + }); + +Usage in commands +~~~~~~~~~~~~~~~~~ + +By default, the Swiftmailer provider sends the emails using the ``KernelEvents::TERMINATE`` +event, which is fired after the response has been sent. However, as this event +isn't fired for console commands, your emails won't be sent. + +For that reason, if you send emails using a command console, it is recommended +that you disable the use of the memory spool (before accessing ``$app['mailer']``):: + + $app['swiftmailer.use_spool'] = false; + +Alternatively, you can just make sure to flush the message spool by hand before +ending the command execution. To do so, use the following code:: + + $app['swiftmailer.spooltransport'] + ->getSpool() + ->flushQueue($app['swiftmailer.transport']) + ; + +Traits +------ + +``Silex\Application\SwiftmailerTrait`` adds the following shortcuts: + +* **mail**: Sends an email. + +.. code-block:: php + + $app->mail(\Swift_Message::newInstance() + ->setSubject('[YourSite] Feedback') + ->setFrom(array('noreply@yoursite.com')) + ->setTo(array('feedback@yoursite.com')) + ->setBody($request->get('message'))); + +For more information, check out the `Swift Mailer documentation +`_. diff --git a/vendor/silex/silex/doc/providers/translation.rst b/vendor/silex/silex/doc/providers/translation.rst new file mode 100644 index 00000000..145fc18c --- /dev/null +++ b/vendor/silex/silex/doc/providers/translation.rst @@ -0,0 +1,193 @@ +Translation +=========== + +The *TranslationServiceProvider* provides a service for translating your +application into different languages. + +Parameters +---------- + +* **translator.domains** (optional): A mapping of domains/locales/messages. + This parameter contains the translation data for all languages and domains. + +* **locale** (optional): The locale for the translator. You will most likely + want to set this based on some request parameter. Defaults to ``en``. + +* **locale_fallbacks** (optional): Fallback locales for the translator. It will + be used when the current locale has no messages set. Defaults to ``en``. + +Services +-------- + +* **translator**: An instance of `Translator + `_, + that is used for translation. + +* **translator.loader**: An instance of an implementation of the translation + `LoaderInterface + `_, + defaults to an `ArrayLoader + `_. + +* **translator.message_selector**: An instance of `MessageSelector + `_. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\LocaleServiceProvider()); + $app->register(new Silex\Provider\TranslationServiceProvider(), array( + 'locale_fallbacks' => array('en'), + )); + +.. note:: + + Add the Symfony Translation Component as a dependency: + + .. code-block:: bash + + composer require symfony/translation + +Usage +----- + +The Translation provider provides a ``translator`` service and makes use of +the ``translator.domains`` parameter:: + + $app['translator.domains'] = array( + 'messages' => array( + 'en' => array( + 'hello' => 'Hello %name%', + 'goodbye' => 'Goodbye %name%', + ), + 'de' => array( + 'hello' => 'Hallo %name%', + 'goodbye' => 'Tschüss %name%', + ), + 'fr' => array( + 'hello' => 'Bonjour %name%', + 'goodbye' => 'Au revoir %name%', + ), + ), + 'validators' => array( + 'fr' => array( + 'This value should be a valid number.' => 'Cette valeur doit être un nombre.', + ), + ), + ); + + $app->get('/{_locale}/{message}/{name}', function ($message, $name) use ($app) { + return $app['translator']->trans($message, array('%name%' => $name)); + }); + +The above example will result in following routes: + +* ``/en/hello/igor`` will return ``Hello igor``. + +* ``/de/hello/igor`` will return ``Hallo igor``. + +* ``/fr/hello/igor`` will return ``Bonjour igor``. + +* ``/it/hello/igor`` will return ``Hello igor`` (because of the fallback). + +Using Resources +--------------- + +When translations are stored in a file, you can load them as follows:: + + $app = new Application(); + + $app->register(new TranslationServiceProvider()); + $app->extend('translator.resources', function ($resources, $app) { + $resources = array_merge($resources, array( + array('array', array('This value should be a valid number.' => 'Cette valeur doit être un nombre.'), 'fr', 'validators'), + )); + + return $resources; + }); + +Traits +------ + +``Silex\Application\TranslationTrait`` adds the following shortcuts: + +* **trans**: Translates the given message. + +* **transChoice**: Translates the given choice message by choosing a + translation according to a number. + +.. code-block:: php + + $app->trans('Hello World'); + + $app->transChoice('Hello World'); + +Recipes +------- + +YAML-based language files +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Having your translations in PHP files can be inconvenient. This recipe will +show you how to load translations from external YAML files. + +First, add the Symfony ``Config`` and ``Yaml`` components as dependencies: + +.. code-block:: bash + + composer require symfony/config symfony/yaml + +Next, you have to create the language mappings in YAML files. A naming you can +use is ``locales/en.yml``. Just do the mapping in this file as follows: + +.. code-block:: yaml + + hello: Hello %name% + goodbye: Goodbye %name% + +Then, register the ``YamlFileLoader`` on the ``translator`` and add all your +translation files:: + + use Symfony\Component\Translation\Loader\YamlFileLoader; + + $app->extend('translator', function($translator, $app) { + $translator->addLoader('yaml', new YamlFileLoader()); + + $translator->addResource('yaml', __DIR__.'/locales/en.yml', 'en'); + $translator->addResource('yaml', __DIR__.'/locales/de.yml', 'de'); + $translator->addResource('yaml', __DIR__.'/locales/fr.yml', 'fr'); + + return $translator; + }); + +XLIFF-based language files +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Just as you would do with YAML translation files, you first need to add the +Symfony ``Config`` component as a dependency (see above for details). + +Then, similarly, create XLIFF files in your locales directory and add them to +the translator:: + + $translator->addResource('xliff', __DIR__.'/locales/en.xlf', 'en'); + $translator->addResource('xliff', __DIR__.'/locales/de.xlf', 'de'); + $translator->addResource('xliff', __DIR__.'/locales/fr.xlf', 'fr'); + +.. note:: + + The XLIFF loader is already pre-configured by the extension. + +Accessing translations in Twig templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once loaded, the translation service provider is available from within Twig +templates when using the Twig bridge provided by Symfony (see +:doc:`TwigServiceProvider
`): + +.. code-block:: jinja + + {{ 'translation_key'|trans }} + {{ 'translation_key'|transchoice }} + {% trans %}translation_key{% endtrans %} diff --git a/vendor/silex/silex/doc/providers/twig.rst b/vendor/silex/silex/doc/providers/twig.rst new file mode 100644 index 00000000..b713e1a3 --- /dev/null +++ b/vendor/silex/silex/doc/providers/twig.rst @@ -0,0 +1,237 @@ +Twig +==== + +The *TwigServiceProvider* provides integration with the `Twig +`_ template engine. + +Parameters +---------- + +* **twig.path** (optional): Path to the directory containing twig template + files (it can also be an array of paths). + +* **twig.templates** (optional): An associative array of template names to + template contents. Use this if you want to define your templates inline. + +* **twig.options** (optional): An associative array of twig + options. Check out the `twig documentation `_ + for more information. + +* **twig.form.templates** (optional): An array of templates used to render + forms (only available when the ``FormServiceProvider`` is enabled). The + default theme is ``form_div_layout.html.twig``, but you can use the other + built-in themes: ``form_table_layout.html.twig``, + ``bootstrap_3_layout.html.twig``, and + ``bootstrap_3_horizontal_layout.html.twig``. + +* **twig.date.format** (optional): Default format used by the ``date`` + filter. The format string must conform to the format accepted by + `date() `_. + +* **twig.date.interval_format** (optional): Default format used by the + ``date`` filter when the filtered data is of type `DateInterval `_. + The format string must conform to the format accepted by + `DateInterval::format() `_. + +* **twig.date.timezone** (optional): Default timezone used when formatting + dates. If set to ``null`` the timezone returned by `date_default_timezone_get() `_ + is used. + +* **twig.number_format.decimals** (optional): Default number of decimals + displayed by the ``number_format`` filter. + +* **twig.number_format.decimal_point** (optional): Default separator for + the decimal point used by the ``number_format`` filter. + +* **twig.number_format.thousands_separator** (optional): Default thousands + separator used by the ``number_format`` filter. + +Services +-------- + +* **twig**: The ``Twig_Environment`` instance. The main way of + interacting with Twig. + +* **twig.loader**: The loader for Twig templates which uses the ``twig.path`` + and the ``twig.templates`` options. You can also replace the loader + completely. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\TwigServiceProvider(), array( + 'twig.path' => __DIR__.'/views', + )); + +.. note:: + + Add Twig as a dependency: + + .. code-block:: bash + + composer require twig/twig + +Usage +----- + +The Twig provider provides a ``twig`` service that can render templates:: + + $app->get('/hello/{name}', function ($name) use ($app) { + return $app['twig']->render('hello.twig', array( + 'name' => $name, + )); + }); + +Symfony Components Integration +------------------------------ + +Symfony provides a Twig bridge that provides additional integration between +some Symfony components and Twig. Add it as a dependency: + +.. code-block:: bash + + composer require symfony/twig-bridge + +When present, the ``TwigServiceProvider`` will provide you with the following +additional capabilities. + +* Access to the ``path()`` and ``url()`` functions. You can find more + information in the `Symfony Routing documentation + `_: + + .. code-block:: jinja + + {{ path('homepage') }} + {{ url('homepage') }} {# generates the absolute url http://example.org/ #} + {{ path('hello', {name: 'Fabien'}) }} + {{ url('hello', {name: 'Fabien'}) }} {# generates the absolute url http://example.org/hello/Fabien #} + +* Access to the ``absolute_url()`` and ``relative_path()`` Twig functions. + +Translations Support +~~~~~~~~~~~~~~~~~~~~ + +If you are using the ``TranslationServiceProvider``, you will get the +``trans()`` and ``transchoice()`` functions for translation in Twig templates. +You can find more information in the `Symfony Translation documentation +`_. + +Form Support +~~~~~~~~~~~~ + +If you are using the ``FormServiceProvider``, you will get a set of helpers for +working with forms in templates. You can find more information in the `Symfony +Forms reference +`_. + +Security Support +~~~~~~~~~~~~~~~~ + +If you are using the ``SecurityServiceProvider``, you will have access to the +``is_granted()`` function in templates. You can find more information in the +`Symfony Security documentation +`_. + +Web Link Support +~~~~~~~~~~~~~~~~ + +If you are using the ``symfony/web-link`` component, you will have access to the +``preload()``, ``prefetch()``, ``prerender()``, ``dns_prefetch()``, +``preconnect()`` and ``link()`` functions in templates. You can find more +information in the `Symfony WebLink documentation +`_. + +Global Variable +~~~~~~~~~~~~~~~ + +When the Twig bridge is available, the ``global`` variable refers to an +instance of `AppVariable `_. +It gives access to the following methods: + +.. code-block:: jinja + + {# The current Request #} + {{ global.request }} + + {# The current User (when security is enabled) #} + {{ global.user }} + + {# The current Session #} + {{ global.session }} + + {# The debug flag #} + {{ global.debug }} + +Rendering a Controller +~~~~~~~~~~~~~~~~~~~~~~ + +A ``render`` function is also registered to help you render another controller +from a template (available when the :doc:`HttpFragment Service Provider +
` is registered): + +.. code-block:: jinja + + {{ render(url('sidebar')) }} + + {# or you can reference a controller directly without defining a route for it #} + {{ render(controller(controller)) }} + +.. note:: + + You must prepend the ``app.request.baseUrl`` to render calls to ensure + that the render works when deployed into a sub-directory of the docroot. + +.. note:: + + Read the Twig `reference`_ for Symfony document to learn more about the + various Twig functions. + +Traits +------ + +``Silex\Application\TwigTrait`` adds the following shortcuts: + +* **render**: Renders a view with the given parameters and returns a Response + object. + +.. code-block:: php + + return $app->render('index.html', ['name' => 'Fabien']); + + $response = new Response(); + $response->setTtl(10); + + return $app->render('index.html', ['name' => 'Fabien'], $response); + +.. code-block:: php + + // stream a view + use Symfony\Component\HttpFoundation\StreamedResponse; + + return $app->render('index.html', ['name' => 'Fabien'], new StreamedResponse()); + +* **renderView**: Renders a view with the given parameters and returns a string. + +.. code-block:: php + + $content = $app->renderView('index.html', ['name' => 'Fabien']); + +Customization +------------- + +You can configure the Twig environment before using it by extending the +``twig`` service:: + + $app->extend('twig', function($twig, $app) { + $twig->addGlobal('pi', 3.14); + $twig->addFilter('levenshtein', new \Twig_Filter_Function('levenshtein')); + + return $twig; + }); + +For more information, check out the `official Twig documentation +`_. + +.. _reference: https://symfony.com/doc/current/reference/twig_reference.html#controller diff --git a/vendor/silex/silex/doc/providers/validator.rst b/vendor/silex/silex/doc/providers/validator.rst new file mode 100644 index 00000000..bd4e9985 --- /dev/null +++ b/vendor/silex/silex/doc/providers/validator.rst @@ -0,0 +1,217 @@ +Validator +========= + +The *ValidatorServiceProvider* provides a service for validating data. It is +most useful when used with the *FormServiceProvider*, but can also be used +standalone. + +Parameters +---------- + +* **validator.validator_service_ids**: An array of service names representing + validators. + +Services +-------- + +* **validator**: An instance of `Validator + `_. + +* **validator.mapping.class_metadata_factory**: Factory for metadata loaders, + which can read validation constraint information from classes. Defaults to + StaticMethodLoader--ClassMetadataFactory. + + This means you can define a static ``loadValidatorMetadata`` method on your + data class, which takes a ClassMetadata argument. Then you can set + constraints on this ClassMetadata instance. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\ValidatorServiceProvider()); + +.. note:: + + Add the Symfony Validator Component as a dependency: + + .. code-block:: bash + + composer require symfony/validator + +Usage +----- + +The Validator provider provides a ``validator`` service. + +Validating Values +~~~~~~~~~~~~~~~~~ + +You can validate values directly using the ``validate`` validator +method:: + + use Symfony\Component\Validator\Constraints as Assert; + + $app->get('/validate/{email}', function ($email) use ($app) { + $errors = $app['validator']->validate($email, new Assert\Email()); + + if (count($errors) > 0) { + return (string) $errors; + } else { + return 'The email is valid'; + } + }); + +Validating Associative Arrays +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Validating associative arrays is like validating simple values, with a +collection of constraints:: + + use Symfony\Component\Validator\Constraints as Assert; + + $book = array( + 'title' => 'My Book', + 'author' => array( + 'first_name' => 'Fabien', + 'last_name' => 'Potencier', + ), + ); + + $constraint = new Assert\Collection(array( + 'title' => new Assert\Length(array('min' => 10)), + 'author' => new Assert\Collection(array( + 'first_name' => array(new Assert\NotBlank(), new Assert\Length(array('min' => 10))), + 'last_name' => new Assert\Length(array('min' => 10)), + )), + )); + $errors = $app['validator']->validate($book, $constraint); + + if (count($errors) > 0) { + foreach ($errors as $error) { + echo $error->getPropertyPath().' '.$error->getMessage()."\n"; + } + } else { + echo 'The book is valid'; + } + +Validating Objects +~~~~~~~~~~~~~~~~~~ + +If you want to add validations to a class, you can define the constraint for +the class properties and getters, and then call the ``validate`` method:: + + use Symfony\Component\Validator\Constraints as Assert; + + class Book + { + public $title; + public $author; + } + + class Author + { + public $first_name; + public $last_name; + } + + $author = new Author(); + $author->first_name = 'Fabien'; + $author->last_name = 'Potencier'; + + $book = new Book(); + $book->title = 'My Book'; + $book->author = $author; + + $metadata = $app['validator.mapping.class_metadata_factory']->getMetadataFor('Author'); + $metadata->addPropertyConstraint('first_name', new Assert\NotBlank()); + $metadata->addPropertyConstraint('first_name', new Assert\Length(array('min' => 10))); + $metadata->addPropertyConstraint('last_name', new Assert\Length(array('min' => 10))); + + $metadata = $app['validator.mapping.class_metadata_factory']->getMetadataFor('Book'); + $metadata->addPropertyConstraint('title', new Assert\Length(array('min' => 10))); + $metadata->addPropertyConstraint('author', new Assert\Valid()); + + $errors = $app['validator']->validate($book); + + if (count($errors) > 0) { + foreach ($errors as $error) { + echo $error->getPropertyPath().' '.$error->getMessage()."\n"; + } + } else { + echo 'The author is valid'; + } + +You can also declare the class constraint by adding a static +``loadValidatorMetadata`` method to your classes:: + + use Symfony\Component\Validator\Mapping\ClassMetadata; + use Symfony\Component\Validator\Constraints as Assert; + + class Book + { + public $title; + public $author; + + static public function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('title', new Assert\Length(array('min' => 10))); + $metadata->addPropertyConstraint('author', new Assert\Valid()); + } + } + + class Author + { + public $first_name; + public $last_name; + + static public function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('first_name', new Assert\NotBlank()); + $metadata->addPropertyConstraint('first_name', new Assert\Length(array('min' => 10))); + $metadata->addPropertyConstraint('last_name', new Assert\Length(array('min' => 10))); + } + } + + $app->get('/validate/{email}', function ($email) use ($app) { + $author = new Author(); + $author->first_name = 'Fabien'; + $author->last_name = 'Potencier'; + + $book = new Book(); + $book->title = 'My Book'; + $book->author = $author; + + $errors = $app['validator']->validate($book); + + if (count($errors) > 0) { + foreach ($errors as $error) { + echo $error->getPropertyPath().' '.$error->getMessage()."\n"; + } + } else { + echo 'The author is valid'; + } + }); + +.. note:: + + Use ``addGetterConstraint()`` to add constraints on getter methods and + ``addConstraint()`` to add constraints on the class itself. + +Translation +~~~~~~~~~~~ + +To be able to translate the error messages, you can use the translator +provider and register the messages under the ``validators`` domain:: + + $app['translator.domains'] = array( + 'validators' => array( + 'fr' => array( + 'This value should be a valid number.' => 'Cette valeur doit être un nombre.', + ), + ), + ); + +For more information, consult the `Symfony Validation documentation +`_. diff --git a/vendor/silex/silex/doc/providers/var_dumper.rst b/vendor/silex/silex/doc/providers/var_dumper.rst new file mode 100644 index 00000000..ea4dd19a --- /dev/null +++ b/vendor/silex/silex/doc/providers/var_dumper.rst @@ -0,0 +1,44 @@ +Var Dumper +========== + +The *VarDumperServiceProvider* provides a mechanism that allows exploring then +dumping any PHP variable. + +Parameters +---------- + +* **var_dumper.dump_destination**: A stream URL where dumps should be written + to (defaults to ``null``). + +Services +-------- + +* n/a + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\VarDumperServiceProvider()); + +.. note:: + + Add the Symfony VarDumper Component as a dependency: + + .. code-block:: bash + + composer require symfony/var-dumper + +Usage +----- + +Adding the VarDumper component as a Composer dependency gives you access to the +``dump()`` PHP function anywhere in your code. + +If you are using Twig, it also provides a ``dump()`` Twig function and a +``dump`` Twig tag. + +The VarDumperServiceProvider is also useful when used with the Silex +WebProfiler as the dumps are made available in the web debug toolbar and in the +web profiler. diff --git a/vendor/silex/silex/doc/services.rst b/vendor/silex/silex/doc/services.rst new file mode 100644 index 00000000..8f36e943 --- /dev/null +++ b/vendor/silex/silex/doc/services.rst @@ -0,0 +1,264 @@ +Services +======== + +Silex is not only a framework, it is also a service container. It does this by +extending `Pimple `_ which provides a very simple +service container. + +Dependency Injection +-------------------- + +.. note:: + + You can skip this if you already know what Dependency Injection is. + +Dependency Injection is a design pattern where you pass dependencies to +services instead of creating them from within the service or relying on +globals. This generally leads to code that is decoupled, re-usable, flexible +and testable. + +Here is an example of a class that takes a ``User`` object and stores it as a +file in JSON format:: + + class JsonUserPersister + { + private $basePath; + + public function __construct($basePath) + { + $this->basePath = $basePath; + } + + public function persist(User $user) + { + $data = $user->getAttributes(); + $json = json_encode($data); + $filename = $this->basePath.'/'.$user->id.'.json'; + file_put_contents($filename, $json, LOCK_EX); + } + } + +In this simple example the dependency is the ``basePath`` property. It is +passed to the constructor. This means you can create several independent +instances with different base paths. Of course dependencies do not have to be +simple strings. More often they are in fact other services. + +A service container is responsible for creating and storing services. It can +recursively create dependencies of the requested services and inject them. It +does so lazily, which means a service is only created when you actually need it. + +Pimple +------ + +Pimple makes strong use of closures and implements the ArrayAccess interface. + +We will start off by creating a new instance of Pimple -- and because +``Silex\Application`` extends ``Pimple\Container`` all of this applies to Silex +as well:: + + $container = new Pimple\Container(); + +or:: + + $app = new Silex\Application(); + +Parameters +~~~~~~~~~~ + +You can set parameters (which are usually strings) by setting an array key on +the container:: + + $app['some_parameter'] = 'value'; + +The array key can be any value. By convention dots are used for namespacing:: + + $app['asset.host'] = 'http://cdn.mysite.com/'; + +Reading parameter values is possible with the same syntax:: + + echo $app['some_parameter']; + +Service definitions +~~~~~~~~~~~~~~~~~~~ + +Defining services is no different than defining parameters. You just set an +array key on the container to be a closure. However, when you retrieve the +service, the closure is executed. This allows for lazy service creation:: + + $app['some_service'] = function () { + return new Service(); + }; + +And to retrieve the service, use:: + + $service = $app['some_service']; + +On first invocation, this will create the service; the same instance will then +be returned on any subsequent access. + +Factory services +~~~~~~~~~~~~~~~~ + +If you want a different instance to be returned for each service access, wrap +the service definition with the ``factory()`` method:: + + $app['some_service'] = $app->factory(function () { + return new Service(); + }); + +Every time you call ``$app['some_service']``, a new instance of the service is +created. + +Access container from closure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In many cases you will want to access the service container from within a +service definition closure. For example when fetching services the current +service depends on. + +Because of this, the container is passed to the closure as an argument:: + + $app['some_service'] = function ($app) { + return new Service($app['some_other_service'], $app['some_service.config']); + }; + +Here you can see an example of Dependency Injection. ``some_service`` depends +on ``some_other_service`` and takes ``some_service.config`` as configuration +options. The dependency is only created when ``some_service`` is accessed, and +it is possible to replace either of the dependencies by simply overriding +those definitions. + +Going back to our initial example, here's how we could use the container +to manage its dependencies:: + + $app['user.persist_path'] = '/tmp/users'; + $app['user.persister'] = function ($app) { + return new JsonUserPersister($app['user.persist_path']); + }; + + +Protected closures +~~~~~~~~~~~~~~~~~~ + +Because the container sees closures as factories for services, it will always +execute them when reading them. + +In some cases you will however want to store a closure as a parameter, so that +you can fetch it and execute it yourself -- with your own arguments. + +This is why Pimple allows you to protect your closures from being executed, by +using the ``protect`` method:: + + $app['closure_parameter'] = $app->protect(function ($a, $b) { + return $a + $b; + }); + + // will not execute the closure + $add = $app['closure_parameter']; + + // calling it now + echo $add(2, 3); + +Note that protected closures do not get access to the container. + +Core services +------------- + +Silex defines a range of services. + +* **request_stack**: Controls the lifecycle of requests, an instance of + `RequestStack `_. + It gives you access to ``GET``, ``POST`` parameters and lots more! + + Example usage:: + + $id = $app['request_stack']->getCurrentRequest()->get('id'); + + A request is only available when a request is being served; you can only + access it from within a controller, an application before/after middlewares, + or an error handler. + +* **routes**: The `RouteCollection + `_ + that is used internally. You can add, modify, read routes. + +* **url_generator**: An instance of `UrlGenerator + `_, + using the `RouteCollection + `_ + that is provided through the ``routes`` service. It has a ``generate`` + method, which takes the route name as an argument, followed by an array of + route parameters. + +* **controllers**: The ``Silex\ControllerCollection`` that is used internally. + Check the :doc:`Internals chapter ` for more information. + +* **dispatcher**: The `EventDispatcher + `_ + that is used internally. It is the core of the Symfony system and is used + quite a bit by Silex. + +* **resolver**: The `ControllerResolver + `_ + that is used internally. It takes care of executing the controller with the + right arguments. + +* **kernel**: The `HttpKernel + `_ + that is used internally. The HttpKernel is the heart of Symfony, it takes a + Request as input and returns a Response as output. + +* **request_context**: The request context is a simplified representation of + the request that is used by the router and the URL generator. + +* **exception_handler**: The Exception handler is the default handler that is + used when you don't register one via the ``error()`` method or if your + handler does not return a Response. Disable it with + ``unset($app['exception_handler'])``. + +* **logger**: A `LoggerInterface `_ instance. By default, logging is + disabled as the value is set to ``null``. To enable logging you can either use + the :doc:`MonologServiceProvider ` or define your own ``logger`` service that + conforms to the PSR logger interface. + +Core traits +----------- + +* ``Silex\Application\UrlGeneratorTrait`` adds the following shortcuts: + + * **path**: Generates a path. + + * **url**: Generates an absolute URL. + + .. code-block:: php + + $app->path('homepage'); + $app->url('homepage'); + +Core parameters +--------------- + +* **request.http_port** (optional): Allows you to override the default port + for non-HTTPS URLs. If the current request is HTTP, it will always use the + current port. + + Defaults to 80. + + This parameter can be used when generating URLs. + +* **request.https_port** (optional): Allows you to override the default port + for HTTPS URLs. If the current request is HTTPS, it will always use the + current port. + + Defaults to 443. + + This parameter can be used when generating URLs. + +* **debug** (optional): Returns whether or not the application is running in + debug mode. + + Defaults to false. + +* **charset** (optional): The charset to use for Responses. + + Defaults to UTF-8. diff --git a/vendor/silex/silex/doc/testing.rst b/vendor/silex/silex/doc/testing.rst new file mode 100644 index 00000000..17f5f571 --- /dev/null +++ b/vendor/silex/silex/doc/testing.rst @@ -0,0 +1,222 @@ +Testing +======= + +Because Silex is built on top of Symfony, it is very easy to write functional +tests for your application. Functional tests are automated software tests that +ensure that your code is working correctly. They go through the user interface, +using a fake browser, and mimic the actions a user would do. + +Why +--- + +If you are not familiar with software tests, you may be wondering why you would +need this. Every time you make a change to your application, you have to test +it. This means going through all the pages and making sure they are still +working. Functional tests save you a lot of time, because they enable you to +test your application in usually under a second by running a single command. + +For more information on functional testing, unit testing, and automated +software tests in general, check out `PHPUnit +`_ and `Bulat Shakirzyanov's talk +on Clean Code `_. + +PHPUnit +------- + +`PHPUnit `_ is the de-facto +standard testing framework for PHP. It was built for writing unit tests, but it +can be used for functional tests too. You write tests by creating a new class, +that extends the ``PHPUnit_Framework_TestCase``. Your test cases are methods +prefixed with ``test``:: + + class ContactFormTest extends \PHPUnit_Framework_TestCase + { + public function testInitialPage() + { + ... + } + } + +In your test cases, you do assertions on the state of what you are testing. In +this case we are testing a contact form, so we would want to assert that the +page loaded correctly and contains our form:: + + public function testInitialPage() + { + $statusCode = ... + $pageContent = ... + + $this->assertEquals(200, $statusCode); + $this->assertContains('Contact us', $pageContent); + $this->assertContains('`_ +section of the PHPUnit documentation. + +WebTestCase +----------- + +Symfony provides a WebTestCase class that can be used to write functional +tests. The Silex version of this class is ``Silex\WebTestCase``, and you can +use it by making your test extend it:: + + use Silex\WebTestCase; + + class ContactFormTest extends WebTestCase + { + ... + } + +.. caution:: + + If you need to override the ``setUp()`` method, don't forget to call the + parent (``parent::setUp()``) to call the Silex default setup. + +.. note:: + + If you want to use the Symfony ``WebTestCase`` class you will need to + explicitly install its dependencies for your project: + + .. code-block:: bash + + composer require --dev symfony/browser-kit symfony/css-selector + +For your WebTestCase, you will have to implement a ``createApplication`` +method, which returns your application instance:: + + public function createApplication() + { + // app.php must return an Application instance + return require __DIR__.'/path/to/app.php'; + } + +Make sure you do **not** use ``require_once`` here, as this method will be +executed before every test. + +.. tip:: + + By default, the application behaves in the same way as when using it from a + browser. But when an error occurs, it is sometimes easier to get raw + exceptions instead of HTML pages. It is rather simple if you tweak the + application configuration in the ``createApplication()`` method like + follows:: + + public function createApplication() + { + $app = require __DIR__.'/path/to/app.php'; + $app['debug'] = true; + unset($app['exception_handler']); + + return $app; + } + +.. tip:: + + If your application use sessions, set ``session.test`` to ``true`` to + simulate sessions:: + + public function createApplication() + { + // ... + + $app['session.test'] = true; + + // ... + } + +The WebTestCase provides a ``createClient`` method. A client acts as a browser, +and allows you to interact with your application. Here's how it works:: + + public function testInitialPage() + { + $client = $this->createClient(); + $crawler = $client->request('GET', '/'); + + $this->assertTrue($client->getResponse()->isOk()); + $this->assertCount(1, $crawler->filter('h1:contains("Contact us")')); + $this->assertCount(1, $crawler->filter('form')); + ... + } + +There are several things going on here. You have both a ``Client`` and a +``Crawler``. + +You can also access the application through ``$this->app``. + +Client +~~~~~~ + +The client represents a browser. It holds your browsing history, cookies and +more. The ``request`` method allows you to make a request to a page on your +application. + +.. note:: + + You can find some documentation for it in `the client section of the + testing chapter of the Symfony documentation + `_. + +Crawler +~~~~~~~ + +The crawler allows you to inspect the content of a page. You can filter it +using CSS expressions and lots more. + +.. note:: + + You can find some documentation for it in `the crawler section of the testing + chapter of the Symfony documentation + `_. + +Configuration +------------- + +The suggested way to configure PHPUnit is to create a ``phpunit.xml.dist`` +file, a ``tests`` folder and your tests in +``tests/YourApp/Tests/YourTest.php``. The ``phpunit.xml.dist`` file should +look like this: + +.. code-block:: xml + + + + + + ./tests/ + + + + +Your ``tests/YourApp/Tests/YourTest.php`` should look like this:: + + namespace YourApp\Tests; + + use Silex\WebTestCase; + + class YourTest extends WebTestCase + { + public function createApplication() + { + return require __DIR__.'/../../../app.php'; + } + + public function testFooBar() + { + ... + } + } + +Now, when running ``phpunit`` on the command line, tests should run. diff --git a/vendor/silex/silex/doc/usage.rst b/vendor/silex/silex/doc/usage.rst new file mode 100644 index 00000000..724254ce --- /dev/null +++ b/vendor/silex/silex/doc/usage.rst @@ -0,0 +1,799 @@ +Usage +===== + +Installation +------------ + +If you want to get started fast, use the `Silex Skeleton`_: + +.. code-block:: bash + + composer create-project fabpot/silex-skeleton path/to/install "~2.0" + +If you want more flexibility, use Composer_ instead: + +.. code-block:: bash + + composer require silex/silex:~2.0 + +Web Server +---------- + +All examples in the documentation rely on a well-configured web server; read +the :doc:`webserver documentation` to check yours. + +Bootstrap +--------- + +To bootstrap Silex, all you need to do is require the ``vendor/autoload.php`` +file and create an instance of ``Silex\Application``. After your controller +definitions, call the ``run`` method on your application:: + + // web/index.php + require_once __DIR__.'/../vendor/autoload.php'; + + $app = new Silex\Application(); + + // ... definitions + + $app->run(); + +.. tip:: + + When developing a website, you might want to turn on the debug mode to + ease debugging:: + + $app['debug'] = true; + +.. tip:: + + If your application is hosted behind a reverse proxy at address ``$ip``, + and you want Silex to trust the ``X-Forwarded-For*`` headers, you will + need to run your application like this:: + + use Symfony\Component\HttpFoundation\Request; + + Request::setTrustedProxies(array($ip)); + $app->run(); + +Routing +------- + +In Silex you define a route and the controller that is called when that +route is matched. A route pattern consists of: + +* *Pattern*: The route pattern defines a path that points to a resource. The + pattern can include variable parts and you are able to set RegExp + requirements for them. + +* *Method*: One of the following HTTP methods: ``GET``, ``POST``, ``PUT``, + ``DELETE``, ``PATCH``, or ``OPTIONS``. This describes the interaction with + the resource. + +The controller is defined using a closure like this:: + + function () { + // ... do something + } + +The return value of the closure becomes the content of the page. + +Example GET Route +~~~~~~~~~~~~~~~~~ + +Here is an example definition of a ``GET`` route:: + + $blogPosts = array( + 1 => array( + 'date' => '2011-03-29', + 'author' => 'igorw', + 'title' => 'Using Silex', + 'body' => '...', + ), + ); + + $app->get('/blog', function () use ($blogPosts) { + $output = ''; + foreach ($blogPosts as $post) { + $output .= $post['title']; + $output .= '
'; + } + + return $output; + }); + +Visiting ``/blog`` will return a list of blog post titles. The ``use`` +statement means something different in this context. It tells the closure to +import the ``$blogPosts`` variable from the outer scope. This allows you to use +it from within the closure. + +Dynamic Routing +~~~~~~~~~~~~~~~ + +Now, you can create another controller for viewing individual blog posts:: + + $app->get('/blog/{id}', function (Silex\Application $app, $id) use ($blogPosts) { + if (!isset($blogPosts[$id])) { + $app->abort(404, "Post $id does not exist."); + } + + $post = $blogPosts[$id]; + + return "

{$post['title']}

". + "

{$post['body']}

"; + }); + +This route definition has a variable ``{id}`` part which is passed to the +closure. + +The current ``Application`` is automatically injected by Silex to the Closure +thanks to the type hinting. + +When the post does not exist, you are using ``abort()`` to stop the request +early. It actually throws an exception, which you will see how to handle later +on. + +Example POST Route +~~~~~~~~~~~~~~~~~~ + +POST routes signify the creation of a resource. An example for this is a +feedback form. You will use the ``mail`` function to send an e-mail:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + $app->post('/feedback', function (Request $request) { + $message = $request->get('message'); + mail('feedback@yoursite.com', '[YourSite] Feedback', $message); + + return new Response('Thank you for your feedback!', 201); + }); + +It is pretty straightforward. + +.. note:: + + There is a :doc:`SwiftmailerServiceProvider ` + included that you can use instead of ``mail()``. + +The current ``request`` is automatically injected by Silex to the Closure +thanks to the type hinting. It is an instance of +Request_, so you can fetch variables using the request ``get`` method. + +Instead of returning a string you are returning an instance of Response_. +This allows setting an HTTP status code, in this case it is set to +``201 Created``. + +.. note:: + + Silex always uses a ``Response`` internally, it converts strings to + responses with status code ``200``. + +Other methods +~~~~~~~~~~~~~ + +You can create controllers for most HTTP methods. Just call one of these +methods on your application: ``get``, ``post``, ``put``, ``delete``, ``patch``, ``options``:: + + $app->put('/blog/{id}', function ($id) { + // ... + }); + + $app->delete('/blog/{id}', function ($id) { + // ... + }); + + $app->patch('/blog/{id}', function ($id) { + // ... + }); + +.. tip:: + + Forms in most web browsers do not directly support the use of other HTTP + methods. To use methods other than GET and POST you can utilize a special + form field with a name of ``_method``. The form's ``method`` attribute must + be set to POST when using this field: + + .. code-block:: html + +
+ + +
+ + You need to explicitly enable this method override:: + + use Symfony\Component\HttpFoundation\Request; + + Request::enableHttpMethodParameterOverride(); + $app->run(); + +You can also call ``match``, which will match all methods. This can be +restricted via the ``method`` method:: + + $app->match('/blog', function () { + // ... + }); + + $app->match('/blog', function () { + // ... + }) + ->method('PATCH'); + + $app->match('/blog', function () { + // ... + }) + ->method('PUT|POST'); + +.. note:: + + The order in which the routes are defined is significant. The first + matching route will be used, so place more generic routes at the bottom. + +Route Variables +~~~~~~~~~~~~~~~ + +As it has been shown before you can define variable parts in a route like +this:: + + $app->get('/blog/{id}', function ($id) { + // ... + }); + +It is also possible to have more than one variable part, just make sure the +closure arguments match the names of the variable parts:: + + $app->get('/blog/{postId}/{commentId}', function ($postId, $commentId) { + // ... + }); + +While it's not recommended, you could also do this (note the switched +arguments):: + + $app->get('/blog/{postId}/{commentId}', function ($commentId, $postId) { + // ... + }); + +You can also ask for the current Request and Application objects:: + + $app->get('/blog/{id}', function (Application $app, Request $request, $id) { + // ... + }); + +.. note:: + + Note for the Application and Request objects, Silex does the injection + based on the type hinting and not on the variable name:: + + $app->get('/blog/{id}', function (Application $foo, Request $bar, $id) { + // ... + }); + +Route Variable Converters +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Before injecting the route variables into the controller, you can apply some +converters:: + + $app->get('/user/{id}', function ($id) { + // ... + })->convert('id', function ($id) { return (int) $id; }); + +This is useful when you want to convert route variables to objects as it +allows to reuse the conversion code across different controllers:: + + $userProvider = function ($id) { + return new User($id); + }; + + $app->get('/user/{user}', function (User $user) { + // ... + })->convert('user', $userProvider); + + $app->get('/user/{user}/edit', function (User $user) { + // ... + })->convert('user', $userProvider); + +The converter callback also receives the ``Request`` as its second argument:: + + $callback = function ($post, Request $request) { + return new Post($request->attributes->get('slug')); + }; + + $app->get('/blog/{id}/{slug}', function (Post $post) { + // ... + })->convert('post', $callback); + +A converter can also be defined as a service. For example, here is a user +converter based on Doctrine ObjectManager:: + + use Doctrine\Common\Persistence\ObjectManager; + use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + + class UserConverter + { + private $om; + + public function __construct(ObjectManager $om) + { + $this->om = $om; + } + + public function convert($id) + { + if (null === $user = $this->om->find('User', (int) $id)) { + throw new NotFoundHttpException(sprintf('User %d does not exist', $id)); + } + + return $user; + } + } + +The service will now be registered in the application, and the +``convert()`` method will be used as converter (using the syntax +``service_name:method_name``):: + + $app['converter.user'] = function () { + return new UserConverter(); + }; + + $app->get('/user/{user}', function (User $user) { + // ... + })->convert('user', 'converter.user:convert'); + +Requirements +~~~~~~~~~~~~ + +In some cases you may want to only match certain expressions. You can define +requirements using regular expressions by calling ``assert`` on the +``Controller`` object, which is returned by the routing methods. + +The following will make sure the ``id`` argument is a positive integer, since +``\d+`` matches any amount of digits:: + + $app->get('/blog/{id}', function ($id) { + // ... + }) + ->assert('id', '\d+'); + +You can also chain these calls:: + + $app->get('/blog/{postId}/{commentId}', function ($postId, $commentId) { + // ... + }) + ->assert('postId', '\d+') + ->assert('commentId', '\d+'); + +Conditions +~~~~~~~~~~ + +Besides restricting route matching based on the HTTP method or parameter +requirements, you can set conditions on any part of the request by calling +``when`` on the ``Controller`` object, which is returned by the routing +methods:: + + $app->get('/blog/{id}', function ($id) { + // ... + }) + ->when("request.headers.get('User-Agent') matches '/firefox/i'"); + +The ``when`` argument is a Symfony Expression_ , which means that you need to +add ``symfony/expression-language`` as a dependency of your project. + +Default Values +~~~~~~~~~~~~~~ + +You can define a default value for any route variable by calling ``value`` on +the ``Controller`` object:: + + $app->get('/{pageName}', function ($pageName) { + // ... + }) + ->value('pageName', 'index'); + +This will allow matching ``/``, in which case the ``pageName`` variable will +have the value ``index``. + +Named Routes +~~~~~~~~~~~~ + +Some providers can make use of named routes. By default Silex will generate an +internal route name for you but you can give an explicit route name by calling +``bind``:: + + $app->get('/', function () { + // ... + }) + ->bind('homepage'); + + $app->get('/blog/{id}', function ($id) { + // ... + }) + ->bind('blog_post'); + +Controllers as Classes +~~~~~~~~~~~~~~~~~~~~~~ + +Instead of anonymous functions, you can also define your controllers as +methods. By using the ``ControllerClass::methodName`` syntax, you can tell +Silex to lazily create the controller object for you:: + + $app->get('/', 'Acme\\Foo::bar'); + + use Silex\Application; + use Symfony\Component\HttpFoundation\Request; + + namespace Acme + { + class Foo + { + public function bar(Request $request, Application $app) + { + // ... + } + } + } + +This will load the ``Acme\Foo`` class on demand, create an instance and call +the ``bar`` method to get the response. You can use ``Request`` and +``Silex\Application`` type hints to get ``$request`` and ``$app`` injected. + +It is also possible to :doc:`define your controllers as services +`. + +Global Configuration +-------------------- + +If a controller setting must be applied to **all** controllers (a converter, a +middleware, a requirement, or a default value), configure it on +``$app['controllers']``, which holds all application controllers:: + + $app['controllers'] + ->value('id', '1') + ->assert('id', '\d+') + ->requireHttps() + ->method('get') + ->convert('id', function () { /* ... */ }) + ->before(function () { /* ... */ }) + ->when('request.isSecure() == true') + ; + +These settings are applied to already registered controllers and they become +the defaults for new controllers. + +.. note:: + + The global configuration does not apply to controller providers you might + mount as they have their own global configuration (read the + :doc:`dedicated chapter` for more information). + +Error Handlers +-------------- + +When an exception is thrown, error handlers allow you to display a custom +error page to the user. They can also be used to do additional things, such as +logging. + +To register an error handler, pass a closure to the ``error`` method which +takes an ``Exception`` argument and returns a response:: + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpFoundation\Request; + + $app->error(function (\Exception $e, Request $request, $code) { + return new Response('We are sorry, but something went terribly wrong.'); + }); + +You can also check for specific errors by using the ``$code`` argument, and +handle them differently:: + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpFoundation\Request; + + $app->error(function (\Exception $e, Request $request, $code) { + switch ($code) { + case 404: + $message = 'The requested page could not be found.'; + break; + default: + $message = 'We are sorry, but something went terribly wrong.'; + } + + return new Response($message); + }); + +You can restrict an error handler to only handle some Exception classes by +setting a more specific type hint for the Closure argument:: + + use Symfony\Component\HttpFoundation\Request; + + $app->error(function (\LogicException $e, Request $request, $code) { + // this handler will only handle \LogicException exceptions + // and exceptions that extend \LogicException + }); + +.. note:: + + As Silex ensures that the Response status code is set to the most + appropriate one depending on the exception, setting the status on the + response won't work. If you want to overwrite the status code, set the + ``X-Status-Code`` header:: + + return new Response('Error', 404 /* ignored */, array('X-Status-Code' => 200)); + +If you want to use a separate error handler for logging, make sure you register +it with a higher priority than response error handlers, because once a response +is returned, the following handlers are ignored. + +.. note:: + + Silex ships with a provider for Monolog_ which handles logging of errors. + Check out the *Providers* :doc:`chapter ` for details. + +.. tip:: + + Silex comes with a default error handler that displays a detailed error + message with the stack trace when **debug** is true, and a simple error + message otherwise. Error handlers registered via the ``error()`` method + always take precedence but you can keep the nice error messages when debug + is turned on like this:: + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpFoundation\Request; + + $app->error(function (\Exception $e, Request $request, $code) use ($app) { + if ($app['debug']) { + return; + } + + // ... logic to handle the error and return a Response + }); + +The error handlers are also called when you use ``abort`` to abort a request +early:: + + $app->get('/blog/{id}', function (Silex\Application $app, $id) use ($blogPosts) { + if (!isset($blogPosts[$id])) { + $app->abort(404, "Post $id does not exist."); + } + + return new Response(...); + }); + +You can convert errors to ``Exceptions``, check out the cookbook :doc:`chapter ` for details. + +View Handlers +------------- + +View Handlers allow you to intercept a controller result that is not a +``Response`` and transform it before it gets returned to the kernel. + +To register a view handler, pass a callable (or string that can be resolved to a +callable) to the ``view()`` method. The callable should accept some sort of result +from the controller:: + + $app->view(function (array $controllerResult) use ($app) { + return $app->json($controllerResult); + }); + +View Handlers also receive the ``Request`` as their second argument, +making them a good candidate for basic content negotiation:: + + $app->view(function (array $controllerResult, Request $request) use ($app) { + $acceptHeader = $request->headers->get('Accept'); + $bestFormat = $app['negotiator']->getBestFormat($acceptHeader, array('json', 'xml')); + + if ('json' === $bestFormat) { + return new JsonResponse($controllerResult); + } + + if ('xml' === $bestFormat) { + return $app['serializer.xml']->renderResponse($controllerResult); + } + + return $controllerResult; + }); + +View Handlers will be examined in the order they are added to the application +and Silex will use type hints to determine if a view handler should be used for +the current result, continuously using the return value of the last view handler +as the input for the next. + +.. note:: + + You must ensure that Silex receives a ``Response`` or a string as the result of + the last view handler (or controller) to be run. + +Redirects +--------- + +You can redirect to another page by returning a ``RedirectResponse`` response, +which you can create by calling the ``redirect`` method:: + + $app->get('/', function () use ($app) { + return $app->redirect('/hello'); + }); + +This will redirect from ``/`` to ``/hello``. + +Forwards +-------- + +When you want to delegate the rendering to another controller, without a +round-trip to the browser (as for a redirect), use an internal sub-request:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpKernel\HttpKernelInterface; + + $app->get('/', function () use ($app) { + // forward to /hello + $subRequest = Request::create('/hello', 'GET'); + + return $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + }); + +.. tip:: + + You can also generate the URI via the built-in URL generator:: + + $request = Request::create($app['url_generator']->generate('hello'), 'GET'); + +There's some more things that you need to keep in mind though. In most cases you +will want to forward some parts of the current master request to the sub-request. +That includes: Cookies, server information, session. +Read more on :doc:`how to make sub-requests `. + +JSON +---- + +If you want to return JSON data, you can use the ``json`` helper method. +Simply pass it your data, status code and headers, and it will create a JSON +response for you:: + + $app->get('/users/{id}', function ($id) use ($app) { + $user = getUser($id); + + if (!$user) { + $error = array('message' => 'The user was not found.'); + + return $app->json($error, 404); + } + + return $app->json($user); + }); + +Streaming +--------- + +It's possible to stream a response, which is important in cases when you don't +want to buffer the data being sent:: + + $app->get('/images/{file}', function ($file) use ($app) { + if (!file_exists(__DIR__.'/images/'.$file)) { + return $app->abort(404, 'The image was not found.'); + } + + $stream = function () use ($file) { + readfile($file); + }; + + return $app->stream($stream, 200, array('Content-Type' => 'image/png')); + }); + +If you need to send chunks, make sure you call ``ob_flush`` and ``flush`` +after every chunk:: + + $stream = function () { + $fh = fopen('http://www.example.com/', 'rb'); + while (!feof($fh)) { + echo fread($fh, 1024); + ob_flush(); + flush(); + } + fclose($fh); + }; + +Sending a file +-------------- + +If you want to return a file, you can use the ``sendFile`` helper method. +It eases returning files that would otherwise not be publicly available. Simply +pass it your file path, status code, headers and the content disposition and it +will create a ``BinaryFileResponse`` response for you:: + + $app->get('/files/{path}', function ($path) use ($app) { + if (!file_exists('/base/path/' . $path)) { + $app->abort(404); + } + + return $app->sendFile('/base/path/' . $path); + }); + +To further customize the response before returning it, check the API doc for +`Symfony\Component\HttpFoundation\BinaryFileResponse +`_:: + + return $app + ->sendFile('/base/path/' . $path) + ->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'pic.jpg') + ; + +Traits +------ + +Silex comes with PHP traits that define shortcut methods. + +Almost all built-in service providers have some corresponding PHP traits. To +use them, define your own Application class and include the traits you want:: + + use Silex\Application; + + class MyApplication extends Application + { + use Application\TwigTrait; + use Application\SecurityTrait; + use Application\FormTrait; + use Application\UrlGeneratorTrait; + use Application\SwiftmailerTrait; + use Application\MonologTrait; + use Application\TranslationTrait; + } + +You can also define your own Route class and use some traits:: + + use Silex\Route; + + class MyRoute extends Route + { + use Route\SecurityTrait; + } + +To use your newly defined route, override the ``$app['route_class']`` +setting:: + + $app['route_class'] = 'MyRoute'; + +Read each provider chapter to learn more about the added methods. + +Security +-------- + +Make sure to protect your application against attacks. + +Escaping +~~~~~~~~ + +When outputting any user input, make sure to escape it correctly to prevent +Cross-Site-Scripting attacks. + +* **Escaping HTML**: PHP provides the ``htmlspecialchars`` function for this. + Silex provides a shortcut ``escape`` method:: + + use Symfony\Component\HttpFoundation\Request; + + $app->get('/name', function (Request $request, Silex\Application $app) { + $name = $request->get('name'); + + return "You provided the name {$app->escape($name)}."; + }); + + If you use the Twig template engine, you should use its escaping or even + auto-escaping mechanisms. Check out the *Providers* :doc:`chapter ` for details. + +* **Escaping JSON**: If you want to provide data in JSON format you should + use the Silex ``json`` function:: + + use Symfony\Component\HttpFoundation\Request; + + $app->get('/name.json', function (Request $request, Silex\Application $app) { + $name = $request->get('name'); + + return $app->json(array('name' => $name)); + }); + +.. _Silex Skeleton: http://github.com/silexphp/Silex-Skeleton +.. _Composer: http://getcomposer.org/ +.. _Request: http://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html +.. _Response: http://api.symfony.com/master/Symfony/Component/HttpFoundation/Response.html +.. _Monolog: https://github.com/Seldaek/monolog +.. _Expression: https://symfony.com/doc/current/book/routing.html#completely-customized-route-matching-with-conditions diff --git a/vendor/silex/silex/doc/web_servers.rst b/vendor/silex/silex/doc/web_servers.rst new file mode 100644 index 00000000..4fd2dc74 --- /dev/null +++ b/vendor/silex/silex/doc/web_servers.rst @@ -0,0 +1,183 @@ +Webserver Configuration +======================= + +Apache +------ + +If you are using Apache, make sure ``mod_rewrite`` is enabled and use the +following ``.htaccess`` file: + +.. code-block:: apache + + + Options -MultiViews + + RewriteEngine On + #RewriteBase /path/to/app + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [QSA,L] + + +.. note:: + + If your site is not at the webroot level you will have to uncomment the + ``RewriteBase`` statement and adjust the path to point to your directory, + relative from the webroot. + +Alternatively, if you use Apache 2.2.16 or higher, you can use the +`FallbackResource directive`_ to make your .htaccess even easier: + +.. code-block:: apache + + FallbackResource index.php + +.. note:: + + If your site is not at the webroot level you will have to adjust the path to + point to your directory, relative from the webroot. + +Or if you're using a VirtualHost, you can add the same directive to the VirtualHost's Directory entry: + +.. code-block:: apache + + + # other directives + + + # other directives + + FallbackResource /index.php + + + +.. note:: + + Note that you need the leading forward slash there, unlike with the .htaccess version + +nginx +----- + +The **minimum configuration** to get your application running under Nginx is: + +.. code-block:: nginx + + server { + server_name domain.tld www.domain.tld; + root /var/www/project/web; + + location / { + # try to serve file directly, fallback to front controller + try_files $uri /index.php$is_args$args; + } + + # If you have 2 front controllers for dev|prod use the following line instead + # location ~ ^/(index|index_dev)\.php(/|$) { + location ~ ^/index\.php(/|$) { + # the ubuntu default + fastcgi_pass unix:/var/run/php/phpX.X-fpm.sock; + # for running on centos + #fastcgi_pass unix:/var/run/php-fpm/www.sock; + + fastcgi_split_path_info ^(.+\.php)(/.*)$; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param HTTPS off; + + # Prevents URIs that include the front controller. This will 404: + # http://domain.tld/index.php/some-path + # Enable the internal directive to disable URIs like this + # internal; + } + + #return 404 for all php files as we do have a front controller + location ~ \.php$ { + return 404; + } + + error_log /var/log/nginx/project_error.log; + access_log /var/log/nginx/project_access.log; + } + +IIS +--- + +If you are using the Internet Information Services from Windows, you can use +this sample ``web.config`` file: + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + + +Lighttpd +-------- + +If you are using lighttpd, use this sample ``simple-vhost`` as a starting +point: + +.. code-block:: lighttpd + + server.document-root = "/path/to/app" + + url.rewrite-once = ( + # configure some static files + "^/assets/.+" => "$0", + "^/favicon\.ico$" => "$0", + + "^(/[^\?]*)(\?.*)?" => "/index.php$1$2" + ) + +.. _FallbackResource directive: http://www.adayinthelifeof.nl/2012/01/21/apaches-fallbackresource-your-new-htaccess-command/ + +PHP +--- + +PHP ships with a built-in webserver for development. This server allows you to +run silex without any configuration. However, in order to serve static files, +you'll have to make sure your front controller returns false in that case:: + + // web/index.php + + $filename = __DIR__.preg_replace('#(\?.*)$#', '', $_SERVER['REQUEST_URI']); + if (php_sapi_name() === 'cli-server' && is_file($filename)) { + return false; + } + + $app = require __DIR__.'/../src/app.php'; + $app->run(); + + +Assuming your front controller is at ``web/index.php``, you can start the +server from the command-line with this command: + +.. code-block:: text + + $ php -S localhost:8080 -t web web/index.php + +Now the application should be running at ``http://localhost:8080``. + +.. note:: + + This server is for development only. It is **not** recommended to use it + in production. diff --git a/vendor/silex/silex/phpunit.xml.dist b/vendor/silex/silex/phpunit.xml.dist new file mode 100644 index 00000000..799f16c9 --- /dev/null +++ b/vendor/silex/silex/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + + + ./tests/Silex/ + + + + + ./src + + + diff --git a/vendor/silex/silex/src/Silex/Api/BootableProviderInterface.php b/vendor/silex/silex/src/Silex/Api/BootableProviderInterface.php new file mode 100644 index 00000000..739e04d5 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Api/BootableProviderInterface.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 Silex\Api; + +use Silex\Application; + +/** + * Interface for bootable service providers. + * + * @author Fabien Potencier + */ +interface BootableProviderInterface +{ + /** + * Bootstraps the application. + * + * This method is called after all services are registered + * and should be used for "dynamic" configuration (whenever + * a service must be requested). + * + * @param Application $app + */ + public function boot(Application $app); +} diff --git a/vendor/silex/silex/src/Silex/Api/ControllerProviderInterface.php b/vendor/silex/silex/src/Silex/Api/ControllerProviderInterface.php new file mode 100644 index 00000000..28d9d0e5 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Api/ControllerProviderInterface.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 Silex\Api; + +use Silex\Application; +use Silex\ControllerCollection; + +/** + * Interface for controller providers. + * + * @author Fabien Potencier + */ +interface ControllerProviderInterface +{ + /** + * Returns routes to connect to the given application. + * + * @param Application $app An Application instance + * + * @return ControllerCollection A ControllerCollection instance + */ + public function connect(Application $app); +} diff --git a/vendor/silex/silex/src/Silex/Api/EventListenerProviderInterface.php b/vendor/silex/silex/src/Silex/Api/EventListenerProviderInterface.php new file mode 100644 index 00000000..f3e62555 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Api/EventListenerProviderInterface.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 Silex\Api; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Pimple\Container; + +/** + * Interface for event listener providers. + * + * @author Fabien Potencier + */ +interface EventListenerProviderInterface +{ + public function subscribe(Container $app, EventDispatcherInterface $dispatcher); +} diff --git a/vendor/silex/silex/src/Silex/Api/LICENSE b/vendor/silex/silex/src/Silex/Api/LICENSE new file mode 100644 index 00000000..bc6ad049 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Api/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-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/vendor/silex/silex/src/Silex/Api/composer.json b/vendor/silex/silex/src/Silex/Api/composer.json new file mode 100644 index 00000000..2cb90bcd --- /dev/null +++ b/vendor/silex/silex/src/Silex/Api/composer.json @@ -0,0 +1,34 @@ +{ + "minimum-stability": "dev", + "name": "silex/api", + "description": "The Silex interfaces", + "keywords": ["microframework"], + "homepage": "http://silex.sensiolabs.org", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0" + }, + "suggest": { + "symfony/event-dispatcher": "For EventListenerProviderInterface", + "silex/silex": "For BootableProviderInterface and ControllerProviderInterface" + }, + "autoload": { + "psr-4": { "Silex\\Api\\": "" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + } +} diff --git a/vendor/silex/silex/src/Silex/AppArgumentValueResolver.php b/vendor/silex/silex/src/Silex/AppArgumentValueResolver.php new file mode 100644 index 00000000..cc2197ab --- /dev/null +++ b/vendor/silex/silex/src/Silex/AppArgumentValueResolver.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 Silex; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * HttpKernel Argument Resolver for Silex. + * + * @author Romain Neutron + */ +class AppArgumentValueResolver implements ArgumentValueResolverInterface +{ + private $app; + + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return null !== $argument->getType() && ($argument->getType() === Application::class || is_subclass_of($argument->getType(), Application::class)); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $this->app; + } +} diff --git a/vendor/silex/silex/src/Silex/Application.php b/vendor/silex/silex/src/Silex/Application.php new file mode 100644 index 00000000..07227682 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application.php @@ -0,0 +1,506 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\TerminableInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\JsonResponse; +use Silex\Api\BootableProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Silex\Api\ControllerProviderInterface; +use Silex\Provider\ExceptionHandlerServiceProvider; +use Silex\Provider\RoutingServiceProvider; +use Silex\Provider\HttpKernelServiceProvider; + +/** + * The Silex framework class. + * + * @author Fabien Potencier + */ +class Application extends Container implements HttpKernelInterface, TerminableInterface +{ + const VERSION = '2.1.0'; + + const EARLY_EVENT = 512; + const LATE_EVENT = -512; + + protected $providers = array(); + protected $booted = false; + + /** + * Instantiate a new Application. + * + * Objects and parameters can be passed as argument to the constructor. + * + * @param array $values The parameters or objects. + */ + public function __construct(array $values = array()) + { + parent::__construct(); + + $this['request.http_port'] = 80; + $this['request.https_port'] = 443; + $this['debug'] = false; + $this['charset'] = 'UTF-8'; + $this['logger'] = null; + + $this->register(new HttpKernelServiceProvider()); + $this->register(new RoutingServiceProvider()); + $this->register(new ExceptionHandlerServiceProvider()); + + foreach ($values as $key => $value) { + $this[$key] = $value; + } + } + + /** + * Registers a service provider. + * + * @param ServiceProviderInterface $provider A ServiceProviderInterface instance + * @param array $values An array of values that customizes the provider + * + * @return Application + */ + public function register(ServiceProviderInterface $provider, array $values = array()) + { + $this->providers[] = $provider; + + parent::register($provider, $values); + + return $this; + } + + /** + * Boots all service providers. + * + * This method is automatically called by handle(), but you can use it + * to boot all service providers when not handling a request. + */ + public function boot() + { + if ($this->booted) { + return; + } + + $this->booted = true; + + foreach ($this->providers as $provider) { + if ($provider instanceof EventListenerProviderInterface) { + $provider->subscribe($this, $this['dispatcher']); + } + + if ($provider instanceof BootableProviderInterface) { + $provider->boot($this); + } + } + } + + /** + * Maps a pattern to a callable. + * + * You can optionally specify HTTP methods that should be matched. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function match($pattern, $to = null) + { + return $this['controllers']->match($pattern, $to); + } + + /** + * Maps a GET request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function get($pattern, $to = null) + { + return $this['controllers']->get($pattern, $to); + } + + /** + * Maps a POST request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function post($pattern, $to = null) + { + return $this['controllers']->post($pattern, $to); + } + + /** + * Maps a PUT request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function put($pattern, $to = null) + { + return $this['controllers']->put($pattern, $to); + } + + /** + * Maps a DELETE request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function delete($pattern, $to = null) + { + return $this['controllers']->delete($pattern, $to); + } + + /** + * Maps an OPTIONS request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function options($pattern, $to = null) + { + return $this['controllers']->options($pattern, $to); + } + + /** + * Maps a PATCH request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function patch($pattern, $to = null) + { + return $this['controllers']->patch($pattern, $to); + } + + /** + * Adds an event listener that listens on the specified events. + * + * @param string $eventName The event to listen on + * @param callable $callback The listener + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function on($eventName, $callback, $priority = 0) + { + if ($this->booted) { + $this['dispatcher']->addListener($eventName, $this['callback_resolver']->resolveCallback($callback), $priority); + + return; + } + + $this->extend('dispatcher', function (EventDispatcherInterface $dispatcher, $app) use ($callback, $priority, $eventName) { + $dispatcher->addListener($eventName, $app['callback_resolver']->resolveCallback($callback), $priority); + + return $dispatcher; + }); + } + + /** + * Registers a before filter. + * + * Before filters are run before any route has been matched. + * + * @param mixed $callback Before filter callback + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function before($callback, $priority = 0) + { + $app = $this; + + $this->on(KernelEvents::REQUEST, function (GetResponseEvent $event) use ($callback, $app) { + if (!$event->isMasterRequest()) { + return; + } + + $ret = call_user_func($app['callback_resolver']->resolveCallback($callback), $event->getRequest(), $app); + + if ($ret instanceof Response) { + $event->setResponse($ret); + } + }, $priority); + } + + /** + * Registers an after filter. + * + * After filters are run after the controller has been executed. + * + * @param mixed $callback After filter callback + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function after($callback, $priority = 0) + { + $app = $this; + + $this->on(KernelEvents::RESPONSE, function (FilterResponseEvent $event) use ($callback, $app) { + if (!$event->isMasterRequest()) { + return; + } + + $response = call_user_func($app['callback_resolver']->resolveCallback($callback), $event->getRequest(), $event->getResponse(), $app); + if ($response instanceof Response) { + $event->setResponse($response); + } elseif (null !== $response) { + throw new \RuntimeException('An after middleware returned an invalid response value. Must return null or an instance of Response.'); + } + }, $priority); + } + + /** + * Registers a finish filter. + * + * Finish filters are run after the response has been sent. + * + * @param mixed $callback Finish filter callback + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function finish($callback, $priority = 0) + { + $app = $this; + + $this->on(KernelEvents::TERMINATE, function (PostResponseEvent $event) use ($callback, $app) { + call_user_func($app['callback_resolver']->resolveCallback($callback), $event->getRequest(), $event->getResponse(), $app); + }, $priority); + } + + /** + * Aborts the current request by sending a proper HTTP error. + * + * @param int $statusCode The HTTP status code + * @param string $message The status message + * @param array $headers An array of HTTP headers + */ + public function abort($statusCode, $message = '', array $headers = array()) + { + throw new HttpException($statusCode, $message, null, $headers); + } + + /** + * Registers an error handler. + * + * Error handlers are simple callables which take a single Exception + * as an argument. If a controller throws an exception, an error handler + * can return a specific response. + * + * When an exception occurs, all handlers will be called, until one returns + * something (a string or a Response object), at which point that will be + * returned to the client. + * + * For this reason you should add logging handlers before output handlers. + * + * @param mixed $callback Error handler callback, takes an Exception argument + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to -8) + */ + public function error($callback, $priority = -8) + { + $this->on(KernelEvents::EXCEPTION, new ExceptionListenerWrapper($this, $callback), $priority); + } + + /** + * Registers a view handler. + * + * View handlers are simple callables which take a controller result and the + * request as arguments, whenever a controller returns a value that is not + * an instance of Response. When this occurs, all suitable handlers will be + * called, until one returns a Response object. + * + * @param mixed $callback View handler callback + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function view($callback, $priority = 0) + { + $this->on(KernelEvents::VIEW, new ViewListenerWrapper($this, $callback), $priority); + } + + /** + * Flushes the controller collection. + */ + public function flush() + { + $this['routes']->addCollection($this['controllers']->flush()); + } + + /** + * Redirects the user to another URL. + * + * @param string $url The URL to redirect to + * @param int $status The status code (302 by default) + * + * @return RedirectResponse + */ + public function redirect($url, $status = 302) + { + return new RedirectResponse($url, $status); + } + + /** + * Creates a streaming response. + * + * @param mixed $callback A valid PHP callback + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return StreamedResponse + */ + public function stream($callback = null, $status = 200, array $headers = array()) + { + return new StreamedResponse($callback, $status, $headers); + } + + /** + * Escapes a text for HTML. + * + * @param string $text The input text to be escaped + * @param int $flags The flags (@see htmlspecialchars) + * @param string $charset The charset + * @param bool $doubleEncode Whether to try to avoid double escaping or not + * + * @return string Escaped text + */ + public function escape($text, $flags = ENT_COMPAT, $charset = null, $doubleEncode = true) + { + return htmlspecialchars($text, $flags, $charset ?: $this['charset'], $doubleEncode); + } + + /** + * Convert some data into a JSON response. + * + * @param mixed $data The response data + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return JsonResponse + */ + public function json($data = array(), $status = 200, array $headers = array()) + { + return new JsonResponse($data, $status, $headers); + } + + /** + * Sends a file. + * + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code + * @param array $headers An array of response headers + * @param null|string $contentDisposition The type of Content-Disposition to set automatically with the filename + * + * @return BinaryFileResponse + */ + public function sendFile($file, $status = 200, array $headers = array(), $contentDisposition = null) + { + return new BinaryFileResponse($file, $status, $headers, true, $contentDisposition); + } + + /** + * Mounts controllers under the given route prefix. + * + * @param string $prefix The route prefix + * @param ControllerCollection|callable|ControllerProviderInterface $controllers A ControllerCollection, a callable, or a ControllerProviderInterface instance + * + * @return Application + * + * @throws \LogicException + */ + public function mount($prefix, $controllers) + { + if ($controllers instanceof ControllerProviderInterface) { + $connectedControllers = $controllers->connect($this); + + if (!$connectedControllers instanceof ControllerCollection) { + throw new \LogicException(sprintf('The method "%s::connect" must return a "ControllerCollection" instance. Got: "%s"', get_class($controllers), is_object($connectedControllers) ? get_class($connectedControllers) : gettype($connectedControllers))); + } + + $controllers = $connectedControllers; + } elseif (!$controllers instanceof ControllerCollection && !is_callable($controllers)) { + throw new \LogicException('The "mount" method takes either a "ControllerCollection" instance, "ControllerProviderInterface" instance, or a callable.'); + } + + $this['controllers']->mount($prefix, $controllers); + + return $this; + } + + /** + * Handles the request and delivers the response. + * + * @param Request|null $request Request to process + */ + public function run(Request $request = null) + { + if (null === $request) { + $request = Request::createFromGlobals(); + } + + $response = $this->handle($request); + $response->send(); + $this->terminate($request, $response); + } + + /** + * {@inheritdoc} + * + * If you call this method directly instead of run(), you must call the + * terminate() method yourself if you want the finish filters to be run. + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + if (!$this->booted) { + $this->boot(); + } + + $this->flush(); + + return $this['kernel']->handle($request, $type, $catch); + } + + /** + * {@inheritdoc} + */ + public function terminate(Request $request, Response $response) + { + $this['kernel']->terminate($request, $response); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/FormTrait.php b/vendor/silex/silex/src/Silex/Application/FormTrait.php new file mode 100644 index 00000000..2eeb23e4 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/FormTrait.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 Silex\Application; + +use Symfony\Component\Form; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\FormBuilder; +use Symfony\Component\OptionsResolver\OptionsResolver\FormTypeInterface; + +/** + * Form trait. + * + * @author Fabien Potencier + * @author David Berlioz + */ +trait FormTrait +{ + /** + * Creates and returns a form builder instance. + * + * @param mixed $data The initial data for the form + * @param array $options Options for the form + * @param string|FormTypeInterface $type Type of the form + * + * @return FormBuilder + */ + public function form($data = null, array $options = array(), $type = null) + { + return $this['form.factory']->createBuilder($type ?: FormType::class, $data, $options); + } + + /** + * Creates and returns a named form builder instance. + * + * @param string $name + * @param mixed $data The initial data for the form + * @param array $options Options for the form + * @param string|FormTypeInterface $type Type of the form + * + * @return FormBuilder + */ + public function namedForm($name, $data = null, array $options = array(), $type = null) + { + return $this['form.factory']->createNamedBuilder($name, $type ?: FormType::class, $data, $options); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/MonologTrait.php b/vendor/silex/silex/src/Silex/Application/MonologTrait.php new file mode 100644 index 00000000..18cb54c6 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/MonologTrait.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 Silex\Application; + +use Monolog\Logger; + +/** + * Monolog trait. + * + * @author Fabien Potencier + */ +trait MonologTrait +{ + /** + * Adds a log record. + * + * @param string $message The log message + * @param array $context The log context + * @param int $level The logging level + * + * @return bool Whether the record has been processed + */ + public function log($message, array $context = array(), $level = Logger::INFO) + { + return $this['monolog']->addRecord($level, $message, $context); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/SecurityTrait.php b/vendor/silex/silex/src/Silex/Application/SecurityTrait.php new file mode 100644 index 00000000..43ce5552 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/SecurityTrait.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 Silex\Application; + +use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Security trait. + * + * @author Fabien Potencier + */ +trait SecurityTrait +{ + /** + * Encodes the raw password. + * + * @param UserInterface $user A UserInterface instance + * @param string $password The password to encode + * + * @return string The encoded password + * + * @throws \RuntimeException when no password encoder could be found for the user + */ + public function encodePassword(UserInterface $user, $password) + { + return $this['security.encoder_factory']->getEncoder($user)->encodePassword($password, $user->getSalt()); + } + + /** + * Checks if the attributes are granted against the current authentication token and optionally supplied object. + * + * @param mixed $attributes + * @param mixed $object + * + * @return bool + * + * @throws AuthenticationCredentialsNotFoundException when the token storage has no authentication token. + */ + public function isGranted($attributes, $object = null) + { + return $this['security.authorization_checker']->isGranted($attributes, $object); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/SwiftmailerTrait.php b/vendor/silex/silex/src/Silex/Application/SwiftmailerTrait.php new file mode 100644 index 00000000..157f94d8 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/SwiftmailerTrait.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 Silex\Application; + +/** + * Swiftmailer trait. + * + * @author Fabien Potencier + */ +trait SwiftmailerTrait +{ + /** + * Sends an email. + * + * @param \Swift_Message $message A \Swift_Message instance + * @param array $failedRecipients An array of failures by-reference + * + * @return int The number of sent messages + */ + public function mail(\Swift_Message $message, &$failedRecipients = null) + { + return $this['mailer']->send($message, $failedRecipients); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/TranslationTrait.php b/vendor/silex/silex/src/Silex/Application/TranslationTrait.php new file mode 100644 index 00000000..8b6e818e --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/TranslationTrait.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 Silex\Application; + +/** + * Translation trait. + * + * @author Fabien Potencier + */ +trait TranslationTrait +{ + /** + * Translates the given message. + * + * @param string $id The message id + * @param array $parameters An array of parameters for the message + * @param string $domain The domain for the message + * @param string $locale The locale + * + * @return string The translated string + */ + public function trans($id, array $parameters = array(), $domain = 'messages', $locale = null) + { + return $this['translator']->trans($id, $parameters, $domain, $locale); + } + + /** + * Translates the given choice message by choosing a translation according to a number. + * + * @param string $id The message id + * @param int $number The number to use to find the indice of the message + * @param array $parameters An array of parameters for the message + * @param string $domain The domain for the message + * @param string $locale The locale + * + * @return string The translated string + */ + public function transChoice($id, $number, array $parameters = array(), $domain = 'messages', $locale = null) + { + return $this['translator']->transChoice($id, $number, $parameters, $domain, $locale); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/TwigTrait.php b/vendor/silex/silex/src/Silex/Application/TwigTrait.php new file mode 100644 index 00000000..cb4127d7 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/TwigTrait.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 Silex\Application; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * Twig trait. + * + * @author Fabien Potencier + */ +trait TwigTrait +{ + /** + * Renders a view and returns a Response. + * + * To stream a view, pass an instance of StreamedResponse as a third argument. + * + * @param string $view The view name + * @param array $parameters An array of parameters to pass to the view + * @param Response $response A Response instance + * + * @return Response A Response instance + */ + public function render($view, array $parameters = array(), Response $response = null) + { + $twig = $this['twig']; + + if ($response instanceof StreamedResponse) { + $response->setCallback(function () use ($twig, $view, $parameters) { + $twig->display($view, $parameters); + }); + } else { + if (null === $response) { + $response = new Response(); + } + $response->setContent($twig->render($view, $parameters)); + } + + return $response; + } + + /** + * Renders a view. + * + * @param string $view The view name + * @param array $parameters An array of parameters to pass to the view + * + * @return string The rendered view + */ + public function renderView($view, array $parameters = array()) + { + return $this['twig']->render($view, $parameters); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/UrlGeneratorTrait.php b/vendor/silex/silex/src/Silex/Application/UrlGeneratorTrait.php new file mode 100644 index 00000000..7ccdf8ac --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/UrlGeneratorTrait.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 Silex\Application; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * UrlGenerator trait. + * + * @author Fabien Potencier + */ +trait UrlGeneratorTrait +{ + /** + * Generates a path from the given parameters. + * + * @param string $route The name of the route + * @param mixed $parameters An array of parameters + * + * @return string The generated path + */ + public function path($route, $parameters = array()) + { + return $this['url_generator']->generate($route, $parameters, UrlGeneratorInterface::ABSOLUTE_PATH); + } + + /** + * Generates an absolute URL from the given parameters. + * + * @param string $route The name of the route + * @param mixed $parameters An array of parameters + * + * @return string The generated URL + */ + public function url($route, $parameters = array()) + { + return $this['url_generator']->generate($route, $parameters, UrlGeneratorInterface::ABSOLUTE_URL); + } +} diff --git a/vendor/silex/silex/src/Silex/CallbackResolver.php b/vendor/silex/silex/src/Silex/CallbackResolver.php new file mode 100644 index 00000000..692901c2 --- /dev/null +++ b/vendor/silex/silex/src/Silex/CallbackResolver.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 Silex; + +use Pimple\Container; + +class CallbackResolver +{ + const SERVICE_PATTERN = "/[A-Za-z0-9\._\-]+:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/"; + + private $app; + + public function __construct(Container $app) + { + $this->app = $app; + } + + /** + * Returns true if the string is a valid service method representation. + * + * @param string $name + * + * @return bool + */ + public function isValid($name) + { + return is_string($name) && (preg_match(static::SERVICE_PATTERN, $name) || isset($this->app[$name])); + } + + /** + * Returns a callable given its string representation. + * + * @param string $name + * + * @return callable + * + * @throws \InvalidArgumentException In case the method does not exist. + */ + public function convertCallback($name) + { + if (preg_match(static::SERVICE_PATTERN, $name)) { + list($service, $method) = explode(':', $name, 2); + $callback = array($this->app[$service], $method); + } else { + $service = $name; + $callback = $this->app[$name]; + } + + if (!is_callable($callback)) { + throw new \InvalidArgumentException(sprintf('Service "%s" is not callable.', $service)); + } + + return $callback; + } + + /** + * Returns a callable given its string representation if it is a valid service method. + * + * @param string $name + * + * @return string|callable A callable value or the string passed in + * + * @throws \InvalidArgumentException In case the method does not exist. + */ + public function resolveCallback($name) + { + return $this->isValid($name) ? $this->convertCallback($name) : $name; + } +} diff --git a/vendor/silex/silex/src/Silex/Controller.php b/vendor/silex/silex/src/Silex/Controller.php new file mode 100644 index 00000000..9a807559 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Controller.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Silex\Exception\ControllerFrozenException; + +/** + * A wrapper for a controller, mapped to a route. + * + * __call() forwards method-calls to Route, but returns instance of Controller + * listing Route's methods below, so that IDEs know they are valid + * + * @method Controller assert(string $variable, string $regexp) + * @method Controller value(string $variable, mixed $default) + * @method Controller convert(string $variable, mixed $callback) + * @method Controller method(string $method) + * @method Controller requireHttp() + * @method Controller requireHttps() + * @method Controller before(mixed $callback) + * @method Controller after(mixed $callback) + * @method Controller when(string $condition) + * + * @author Igor Wiedler + */ +class Controller +{ + private $route; + private $routeName; + private $isFrozen = false; + + /** + * Constructor. + * + * @param Route $route + */ + public function __construct(Route $route) + { + $this->route = $route; + } + + /** + * Gets the controller's route. + * + * @return Route + */ + public function getRoute() + { + return $this->route; + } + + /** + * Gets the controller's route name. + * + * @return string + */ + public function getRouteName() + { + return $this->routeName; + } + + /** + * Sets the controller's route. + * + * @param string $routeName + * + * @return Controller $this The current Controller instance + */ + public function bind($routeName) + { + if ($this->isFrozen) { + throw new ControllerFrozenException(sprintf('Calling %s on frozen %s instance.', __METHOD__, __CLASS__)); + } + + $this->routeName = $routeName; + + return $this; + } + + public function __call($method, $arguments) + { + if (!method_exists($this->route, $method)) { + throw new \BadMethodCallException(sprintf('Method "%s::%s" does not exist.', get_class($this->route), $method)); + } + + call_user_func_array(array($this->route, $method), $arguments); + + return $this; + } + + /** + * Freezes the controller. + * + * Once the controller is frozen, you can no longer change the route name + */ + public function freeze() + { + $this->isFrozen = true; + } + + public function generateRouteName($prefix) + { + $methods = implode('_', $this->route->getMethods()).'_'; + + $routeName = $methods.$prefix.$this->route->getPath(); + $routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName); + $routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName); + + // Collapse consecutive underscores down into a single underscore. + $routeName = preg_replace('/_+/', '_', $routeName); + + return $routeName; + } +} diff --git a/vendor/silex/silex/src/Silex/ControllerCollection.php b/vendor/silex/silex/src/Silex/ControllerCollection.php new file mode 100644 index 00000000..40368964 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ControllerCollection.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 Silex; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\HttpFoundation\Request; + +/** + * Builds Silex controllers. + * + * It acts as a staging area for routes. You are able to set the route name + * until flush() is called, at which point all controllers are frozen and + * converted to a RouteCollection. + * + * __call() forwards method-calls to Route, but returns instance of ControllerCollection + * listing Route's methods below, so that IDEs know they are valid + * + * @method ControllerCollection assert(string $variable, string $regexp) + * @method ControllerCollection value(string $variable, mixed $default) + * @method ControllerCollection convert(string $variable, mixed $callback) + * @method ControllerCollection method(string $method) + * @method ControllerCollection requireHttp() + * @method ControllerCollection requireHttps() + * @method ControllerCollection before(mixed $callback) + * @method ControllerCollection after(mixed $callback) + * @method ControllerCollection when(string $condition) + * + * @author Igor Wiedler + * @author Fabien Potencier + */ +class ControllerCollection +{ + protected $controllers = array(); + protected $defaultRoute; + protected $defaultController; + protected $prefix; + protected $routesFactory; + protected $controllersFactory; + + public function __construct(Route $defaultRoute, RouteCollection $routesFactory = null, $controllersFactory = null) + { + $this->defaultRoute = $defaultRoute; + $this->routesFactory = $routesFactory; + $this->controllersFactory = $controllersFactory; + $this->defaultController = function (Request $request) { + throw new \LogicException(sprintf('The "%s" route must have code to run when it matches.', $request->attributes->get('_route'))); + }; + } + + /** + * Mounts controllers under the given route prefix. + * + * @param string $prefix The route prefix + * @param ControllerCollection|callable $controllers A ControllerCollection instance or a callable for defining routes + * + * @throws \LogicException + */ + public function mount($prefix, $controllers) + { + if (is_callable($controllers)) { + $collection = $this->controllersFactory ? call_user_func($this->controllersFactory) : new static(new Route(), new RouteCollection()); + call_user_func($controllers, $collection); + $controllers = $collection; + } elseif (!$controllers instanceof self) { + throw new \LogicException('The "mount" method takes either a "ControllerCollection" instance or callable.'); + } + + $controllers->prefix = $prefix; + + $this->controllers[] = $controllers; + } + + /** + * Maps a pattern to a callable. + * + * You can optionally specify HTTP methods that should be matched. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function match($pattern, $to = null) + { + $route = clone $this->defaultRoute; + $route->setPath($pattern); + $this->controllers[] = $controller = new Controller($route); + $route->setDefault('_controller', null === $to ? $this->defaultController : $to); + + return $controller; + } + + /** + * Maps a GET request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function get($pattern, $to = null) + { + return $this->match($pattern, $to)->method('GET'); + } + + /** + * Maps a POST request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function post($pattern, $to = null) + { + return $this->match($pattern, $to)->method('POST'); + } + + /** + * Maps a PUT request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function put($pattern, $to = null) + { + return $this->match($pattern, $to)->method('PUT'); + } + + /** + * Maps a DELETE request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function delete($pattern, $to = null) + { + return $this->match($pattern, $to)->method('DELETE'); + } + + /** + * Maps an OPTIONS request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function options($pattern, $to = null) + { + return $this->match($pattern, $to)->method('OPTIONS'); + } + + /** + * Maps a PATCH request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function patch($pattern, $to = null) + { + return $this->match($pattern, $to)->method('PATCH'); + } + + public function __call($method, $arguments) + { + if (!method_exists($this->defaultRoute, $method)) { + throw new \BadMethodCallException(sprintf('Method "%s::%s" does not exist.', get_class($this->defaultRoute), $method)); + } + + call_user_func_array(array($this->defaultRoute, $method), $arguments); + + foreach ($this->controllers as $controller) { + call_user_func_array(array($controller, $method), $arguments); + } + + return $this; + } + + /** + * Persists and freezes staged controllers. + * + * @return RouteCollection A RouteCollection instance + */ + public function flush() + { + if (null === $this->routesFactory) { + $routes = new RouteCollection(); + } else { + $routes = $this->routesFactory; + } + + return $this->doFlush('', $routes); + } + + private function doFlush($prefix, RouteCollection $routes) + { + if ($prefix !== '') { + $prefix = '/'.trim(trim($prefix), '/'); + } + + foreach ($this->controllers as $controller) { + if ($controller instanceof Controller) { + $controller->getRoute()->setPath($prefix.$controller->getRoute()->getPath()); + if (!$name = $controller->getRouteName()) { + $name = $base = $controller->generateRouteName(''); + $i = 0; + while ($routes->get($name)) { + $name = $base.'_'.++$i; + } + $controller->bind($name); + } + $routes->add($name, $controller->getRoute()); + $controller->freeze(); + } else { + $controller->doFlush($prefix.$controller->prefix, $routes); + } + } + + $this->controllers = array(); + + return $routes; + } +} diff --git a/vendor/silex/silex/src/Silex/ControllerResolver.php b/vendor/silex/silex/src/Silex/ControllerResolver.php new file mode 100644 index 00000000..0a95e15f --- /dev/null +++ b/vendor/silex/silex/src/Silex/ControllerResolver.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 Silex; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpKernel\Controller\ControllerResolver as BaseControllerResolver; +use Symfony\Component\HttpFoundation\Request; + +/** + * Adds Application as a valid argument for controllers. + * + * @author Fabien Potencier + * + * @deprecated This class can be dropped once Symfony 3.0 is not supported anymore. + */ +class ControllerResolver extends BaseControllerResolver +{ + protected $app; + + /** + * Constructor. + * + * @param Application $app An Application instance + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(Application $app, LoggerInterface $logger = null) + { + $this->app = $app; + + parent::__construct($logger); + } + + protected function doGetArguments(Request $request, $controller, array $parameters) + { + foreach ($parameters as $param) { + if ($param->getClass() && $param->getClass()->isInstance($this->app)) { + $request->attributes->set($param->getName(), $this->app); + + break; + } + } + + return parent::doGetArguments($request, $controller, $parameters); + } +} diff --git a/vendor/silex/silex/src/Silex/EventListener/ConverterListener.php b/vendor/silex/silex/src/Silex/EventListener/ConverterListener.php new file mode 100644 index 00000000..2fa93c19 --- /dev/null +++ b/vendor/silex/silex/src/Silex/EventListener/ConverterListener.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 Silex\EventListener; + +use Silex\CallbackResolver; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Routing\RouteCollection; + +/** + * Handles converters. + * + * @author Fabien Potencier + */ +class ConverterListener implements EventSubscriberInterface +{ + protected $routes; + protected $callbackResolver; + + /** + * Constructor. + * + * @param RouteCollection $routes A RouteCollection instance + * @param CallbackResolver $callbackResolver A CallbackResolver instance + */ + public function __construct(RouteCollection $routes, CallbackResolver $callbackResolver) + { + $this->routes = $routes; + $this->callbackResolver = $callbackResolver; + } + + /** + * Handles converters. + * + * @param FilterControllerEvent $event The event to handle + */ + public function onKernelController(FilterControllerEvent $event) + { + $request = $event->getRequest(); + $route = $this->routes->get($request->attributes->get('_route')); + if ($route && $converters = $route->getOption('_converters')) { + foreach ($converters as $name => $callback) { + $callback = $this->callbackResolver->resolveCallback($callback); + + $request->attributes->set($name, call_user_func($callback, $request->attributes->get($name), $request)); + } + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::CONTROLLER => 'onKernelController', + ); + } +} diff --git a/vendor/silex/silex/src/Silex/EventListener/LogListener.php b/vendor/silex/silex/src/Silex/EventListener/LogListener.php new file mode 100644 index 00000000..5f3cc904 --- /dev/null +++ b/vendor/silex/silex/src/Silex/EventListener/LogListener.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 Silex\EventListener; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; + +/** + * Logs request, response, and exceptions. + */ +class LogListener implements EventSubscriberInterface +{ + protected $logger; + protected $exceptionLogFilter; + + public function __construct(LoggerInterface $logger, $exceptionLogFilter = null) + { + $this->logger = $logger; + if (null === $exceptionLogFilter) { + $exceptionLogFilter = function (\Exception $e) { + if ($e instanceof HttpExceptionInterface && $e->getStatusCode() < 500) { + return LogLevel::ERROR; + } + + return LogLevel::CRITICAL; + }; + } + + $this->exceptionLogFilter = $exceptionLogFilter; + } + + /** + * Logs master requests on event KernelEvents::REQUEST. + * + * @param GetResponseEvent $event + */ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $this->logRequest($event->getRequest()); + } + + /** + * Logs master response on event KernelEvents::RESPONSE. + * + * @param FilterResponseEvent $event + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $this->logResponse($event->getResponse()); + } + + /** + * Logs uncaught exceptions on event KernelEvents::EXCEPTION. + * + * @param GetResponseForExceptionEvent $event + */ + public function onKernelException(GetResponseForExceptionEvent $event) + { + $this->logException($event->getException()); + } + + /** + * Logs a request. + * + * @param Request $request + */ + protected function logRequest(Request $request) + { + $this->logger->log(LogLevel::DEBUG, '> '.$request->getMethod().' '.$request->getRequestUri()); + } + + /** + * Logs a response. + * + * @param Response $response + */ + protected function logResponse(Response $response) + { + $message = '< '.$response->getStatusCode(); + + if ($response instanceof RedirectResponse) { + $message .= ' '.$response->getTargetUrl(); + } + + $this->logger->log(LogLevel::DEBUG, $message); + } + + /** + * Logs an exception. + */ + protected function logException(\Exception $e) + { + $this->logger->log(call_user_func($this->exceptionLogFilter, $e), sprintf('%s: %s (uncaught exception) at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()), array('exception' => $e)); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array('onKernelRequest', 0), + KernelEvents::RESPONSE => array('onKernelResponse', 0), + /* + * Priority -4 is used to come after those from SecurityServiceProvider (0) + * but before the error handlers added with Silex\Application::error (defaults to -8) + */ + KernelEvents::EXCEPTION => array('onKernelException', -4), + ); + } +} diff --git a/vendor/silex/silex/src/Silex/EventListener/MiddlewareListener.php b/vendor/silex/silex/src/Silex/EventListener/MiddlewareListener.php new file mode 100644 index 00000000..9b28ff1a --- /dev/null +++ b/vendor/silex/silex/src/Silex/EventListener/MiddlewareListener.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\EventListener; + +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Silex\Application; + +/** + * Manages the route middlewares. + * + * @author Fabien Potencier + */ +class MiddlewareListener implements EventSubscriberInterface +{ + protected $app; + + /** + * Constructor. + * + * @param Application $app An Application instance + */ + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * Runs before filters. + * + * @param GetResponseEvent $event The event to handle + */ + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + $routeName = $request->attributes->get('_route'); + if (!$route = $this->app['routes']->get($routeName)) { + return; + } + + foreach ((array) $route->getOption('_before_middlewares') as $callback) { + $ret = call_user_func($this->app['callback_resolver']->resolveCallback($callback), $request, $this->app); + if ($ret instanceof Response) { + $event->setResponse($ret); + + return; + } elseif (null !== $ret) { + throw new \RuntimeException(sprintf('A before middleware for route "%s" returned an invalid response value. Must return null or an instance of Response.', $routeName)); + } + } + } + + /** + * Runs after filters. + * + * @param FilterResponseEvent $event The event to handle + */ + public function onKernelResponse(FilterResponseEvent $event) + { + $request = $event->getRequest(); + $routeName = $request->attributes->get('_route'); + if (!$route = $this->app['routes']->get($routeName)) { + return; + } + + foreach ((array) $route->getOption('_after_middlewares') as $callback) { + $response = call_user_func($this->app['callback_resolver']->resolveCallback($callback), $request, $event->getResponse(), $this->app); + if ($response instanceof Response) { + $event->setResponse($response); + } elseif (null !== $response) { + throw new \RuntimeException(sprintf('An after middleware for route "%s" returned an invalid response value. Must return null or an instance of Response.', $routeName)); + } + } + } + + public static function getSubscribedEvents() + { + return array( + // this must be executed after the late events defined with before() (and their priority is -512) + KernelEvents::REQUEST => array('onKernelRequest', -1024), + KernelEvents::RESPONSE => array('onKernelResponse', 128), + ); + } +} diff --git a/vendor/silex/silex/src/Silex/EventListener/StringToResponseListener.php b/vendor/silex/silex/src/Silex/EventListener/StringToResponseListener.php new file mode 100644 index 00000000..9fdba5fe --- /dev/null +++ b/vendor/silex/silex/src/Silex/EventListener/StringToResponseListener.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 Silex\EventListener; + +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Response; + +/** + * Converts string responses to proper Response instances. + * + * @author Fabien Potencier + */ +class StringToResponseListener implements EventSubscriberInterface +{ + /** + * Handles string responses. + * + * @param GetResponseForControllerResultEvent $event The event to handle + */ + public function onKernelView(GetResponseForControllerResultEvent $event) + { + $response = $event->getControllerResult(); + + if (!( + null === $response + || is_array($response) + || $response instanceof Response + || (is_object($response) && !method_exists($response, '__toString')) + )) { + $event->setResponse(new Response((string) $response)); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::VIEW => array('onKernelView', -10), + ); + } +} diff --git a/vendor/silex/silex/src/Silex/Exception/ControllerFrozenException.php b/vendor/silex/silex/src/Silex/Exception/ControllerFrozenException.php new file mode 100644 index 00000000..7f0d65f1 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Exception/ControllerFrozenException.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 Silex\Exception; + +/** + * Exception, is thrown when a frozen controller is modified. + * + * @author Igor Wiedler + */ +class ControllerFrozenException extends \RuntimeException +{ +} diff --git a/vendor/silex/silex/src/Silex/ExceptionHandler.php b/vendor/silex/silex/src/Silex/ExceptionHandler.php new file mode 100644 index 00000000..34eb8937 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ExceptionHandler.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 Silex; + +use Symfony\Component\Debug\ExceptionHandler as DebugExceptionHandler; +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Default exception handler. + * + * @author Fabien Potencier + */ +class ExceptionHandler implements EventSubscriberInterface +{ + protected $debug; + + public function __construct($debug) + { + $this->debug = $debug; + } + + public function onSilexError(GetResponseForExceptionEvent $event) + { + $handler = new DebugExceptionHandler($this->debug); + + $exception = $event->getException(); + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + $response = Response::create($handler->getHtml($exception), $exception->getStatusCode(), $exception->getHeaders())->setCharset(ini_get('default_charset')); + + $event->setResponse($response); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array(KernelEvents::EXCEPTION => array('onSilexError', -255)); + } +} diff --git a/vendor/silex/silex/src/Silex/ExceptionListenerWrapper.php b/vendor/silex/silex/src/Silex/ExceptionListenerWrapper.php new file mode 100644 index 00000000..e0d527b0 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ExceptionListenerWrapper.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 Silex; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; + +/** + * Wraps exception listeners. + * + * @author Fabien Potencier + */ +class ExceptionListenerWrapper +{ + protected $app; + protected $callback; + + /** + * Constructor. + * + * @param Application $app An Application instance + * @param callable $callback + */ + public function __construct(Application $app, $callback) + { + $this->app = $app; + $this->callback = $callback; + } + + public function __invoke(GetResponseForExceptionEvent $event) + { + $exception = $event->getException(); + $this->callback = $this->app['callback_resolver']->resolveCallback($this->callback); + + if (!$this->shouldRun($exception)) { + return; + } + + $code = $exception instanceof HttpExceptionInterface ? $exception->getStatusCode() : 500; + + $response = call_user_func($this->callback, $exception, $event->getRequest(), $code); + + $this->ensureResponse($response, $event); + } + + protected function shouldRun(\Exception $exception) + { + if (is_array($this->callback)) { + $callbackReflection = new \ReflectionMethod($this->callback[0], $this->callback[1]); + } elseif (is_object($this->callback) && !$this->callback instanceof \Closure) { + $callbackReflection = new \ReflectionObject($this->callback); + $callbackReflection = $callbackReflection->getMethod('__invoke'); + } else { + $callbackReflection = new \ReflectionFunction($this->callback); + } + + if ($callbackReflection->getNumberOfParameters() > 0) { + $parameters = $callbackReflection->getParameters(); + $expectedException = $parameters[0]; + if ($expectedException->getClass() && !$expectedException->getClass()->isInstance($exception)) { + return false; + } + } + + return true; + } + + protected function ensureResponse($response, GetResponseForExceptionEvent $event) + { + if ($response instanceof Response) { + $event->setResponse($response); + } else { + $viewEvent = new GetResponseForControllerResultEvent($this->app['kernel'], $event->getRequest(), $event->getRequestType(), $response); + $this->app['dispatcher']->dispatch(KernelEvents::VIEW, $viewEvent); + + if ($viewEvent->hasResponse()) { + $event->setResponse($viewEvent->getResponse()); + } + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/AssetServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/AssetServiceProvider.php new file mode 100644 index 00000000..6793f676 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/AssetServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Asset\Packages; +use Symfony\Component\Asset\Package; +use Symfony\Component\Asset\PathPackage; +use Symfony\Component\Asset\UrlPackage; +use Symfony\Component\Asset\Context\RequestStackContext; +use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy; +use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy; + +/** + * Symfony Asset component Provider. + * + * @author Fabien Potencier + */ +class AssetServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['assets.packages'] = function ($app) { + $packages = array(); + foreach ($app['assets.named_packages'] as $name => $package) { + $version = $app['assets.strategy_factory'](isset($package['version']) ? $package['version'] : '', isset($package['version_format']) ? $package['version_format'] : null); + + $packages[$name] = $app['assets.package_factory'](isset($package['base_path']) ? $package['base_path'] : '', isset($package['base_urls']) ? $package['base_urls'] : array(), $version, $name); + } + + return new Packages($app['assets.default_package'], $packages); + }; + + $app['assets.default_package'] = function ($app) { + $version = $app['assets.strategy_factory']($app['assets.version'], $app['assets.version_format']); + + return $app['assets.package_factory']($app['assets.base_path'], $app['assets.base_urls'], $version, 'default'); + }; + + $app['assets.context'] = function ($app) { + return new RequestStackContext($app['request_stack']); + }; + + $app['assets.base_path'] = ''; + $app['assets.base_urls'] = array(); + $app['assets.version'] = null; + $app['assets.version_format'] = null; + + $app['assets.named_packages'] = array(); + + // prototypes + + $app['assets.strategy_factory'] = $app->protect(function ($version, $format) use ($app) { + if (!$version) { + return new EmptyVersionStrategy(); + } + + return new StaticVersionStrategy($version, $format); + }); + + $app['assets.package_factory'] = $app->protect(function ($basePath, $baseUrls, $version, $name) use ($app) { + if ($basePath && $baseUrls) { + throw new \LogicException(sprintf('Asset package "%s" cannot have base URLs and base paths.', $name)); + } + + if (!$baseUrls) { + return new PathPackage($basePath, $version, $app['assets.context']); + } + + return new UrlPackage($baseUrls, $version, $app['assets.context']); + }); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/CsrfServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/CsrfServiceProvider.php new file mode 100644 index 00000000..eb6e882d --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/CsrfServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManager; +use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator; +use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; +use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage; + +/** + * Symfony CSRF Security component Provider. + * + * @author Fabien Potencier + */ +class CsrfServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['csrf.token_manager'] = function ($app) { + return new CsrfTokenManager($app['csrf.token_generator'], $app['csrf.token_storage']); + }; + + $app['csrf.token_storage'] = function ($app) { + if (isset($app['session'])) { + return new SessionTokenStorage($app['session'], $app['csrf.session_namespace']); + } + + return new NativeSessionTokenStorage($app['csrf.session_namespace']); + }; + + $app['csrf.token_generator'] = function ($app) { + return new UriSafeTokenGenerator(); + }; + + $app['csrf.session_namespace'] = '_csrf'; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/DoctrineServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/DoctrineServiceProvider.php new file mode 100644 index 00000000..9c71d5b7 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/DoctrineServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Configuration; +use Doctrine\Common\EventManager; +use Symfony\Bridge\Doctrine\Logger\DbalLogger; + +/** + * Doctrine DBAL Provider. + * + * @author Fabien Potencier + */ +class DoctrineServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['db.default_options'] = array( + 'driver' => 'pdo_mysql', + 'dbname' => null, + 'host' => 'localhost', + 'user' => 'root', + 'password' => null, + ); + + $app['dbs.options.initializer'] = $app->protect(function () use ($app) { + static $initialized = false; + + if ($initialized) { + return; + } + + $initialized = true; + + if (!isset($app['dbs.options'])) { + $app['dbs.options'] = array('default' => isset($app['db.options']) ? $app['db.options'] : array()); + } + + $tmp = $app['dbs.options']; + foreach ($tmp as $name => &$options) { + $options = array_replace($app['db.default_options'], $options); + + if (!isset($app['dbs.default'])) { + $app['dbs.default'] = $name; + } + } + $app['dbs.options'] = $tmp; + }); + + $app['dbs'] = function ($app) { + $app['dbs.options.initializer'](); + + $dbs = new Container(); + foreach ($app['dbs.options'] as $name => $options) { + if ($app['dbs.default'] === $name) { + // we use shortcuts here in case the default has been overridden + $config = $app['db.config']; + $manager = $app['db.event_manager']; + } else { + $config = $app['dbs.config'][$name]; + $manager = $app['dbs.event_manager'][$name]; + } + + $dbs[$name] = function ($dbs) use ($options, $config, $manager) { + return DriverManager::getConnection($options, $config, $manager); + }; + } + + return $dbs; + }; + + $app['dbs.config'] = function ($app) { + $app['dbs.options.initializer'](); + + $configs = new Container(); + $addLogger = isset($app['logger']) && null !== $app['logger'] && class_exists('Symfony\Bridge\Doctrine\Logger\DbalLogger'); + foreach ($app['dbs.options'] as $name => $options) { + $configs[$name] = new Configuration(); + if ($addLogger) { + $configs[$name]->setSQLLogger(new DbalLogger($app['logger'], isset($app['stopwatch']) ? $app['stopwatch'] : null)); + } + } + + return $configs; + }; + + $app['dbs.event_manager'] = function ($app) { + $app['dbs.options.initializer'](); + + $managers = new Container(); + foreach ($app['dbs.options'] as $name => $options) { + $managers[$name] = new EventManager(); + } + + return $managers; + }; + + // shortcuts for the "first" DB + $app['db'] = function ($app) { + $dbs = $app['dbs']; + + return $dbs[$app['dbs.default']]; + }; + + $app['db.config'] = function ($app) { + $dbs = $app['dbs.config']; + + return $dbs[$app['dbs.default']]; + }; + + $app['db.event_manager'] = function ($app) { + $dbs = $app['dbs.event_manager']; + + return $dbs[$app['dbs.default']]; + }; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/ExceptionHandlerServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/ExceptionHandlerServiceProvider.php new file mode 100644 index 00000000..1c6f2028 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/ExceptionHandlerServiceProvider.php @@ -0,0 +1,32 @@ +addSubscriber($app['exception_handler']); + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Form/SilexFormExtension.php b/vendor/silex/silex/src/Silex/Provider/Form/SilexFormExtension.php new file mode 100644 index 00000000..12efbdf2 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Form/SilexFormExtension.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Form; + +use Pimple\Container; +use Symfony\Component\Form\Exception\InvalidArgumentException; +use Symfony\Component\Form\FormExtensionInterface; +use Symfony\Component\Form\FormTypeGuesserChain; + +class SilexFormExtension implements FormExtensionInterface +{ + private $app; + private $types; + private $typeExtensions; + private $guessers; + private $guesserLoaded = false; + private $guesser; + + public function __construct(Container $app, array $types, array $typeExtensions, array $guessers) + { + $this->app = $app; + $this->setTypes($types); + $this->setTypeExtensions($typeExtensions); + $this->setGuessers($guessers); + } + + public function getType($name) + { + if (!isset($this->types[$name])) { + throw new InvalidArgumentException(sprintf('The type "%s" is not the name of a registered form type.', $name)); + } + if (!is_object($this->types[$name])) { + $this->types[$name] = $this->app[$this->types[$name]]; + } + + return $this->types[$name]; + } + + public function hasType($name) + { + return isset($this->types[$name]); + } + + public function getTypeExtensions($name) + { + return isset($this->typeExtensions[$name]) ? $this->typeExtensions[$name] : []; + } + + public function hasTypeExtensions($name) + { + return isset($this->typeExtensions[$name]); + } + + public function getTypeGuesser() + { + if (!$this->guesserLoaded) { + $this->guesserLoaded = true; + + if ($this->guessers) { + $guessers = []; + foreach ($this->guessers as $guesser) { + if (!is_object($guesser)) { + $guesser = $this->app[$guesser]; + } + $guessers[] = $guesser; + } + $this->guesser = new FormTypeGuesserChain($guessers); + } + } + + return $this->guesser; + } + + private function setTypes(array $types) + { + $this->types = []; + foreach ($types as $type) { + if (!is_object($type)) { + if (!isset($this->app[$type])) { + throw new InvalidArgumentException(sprintf('Invalid form type. The silex service "%s" does not exist.', $type)); + } + $this->types[$type] = $type; + } else { + $this->types[get_class($type)] = $type; + } + } + } + + private function setTypeExtensions(array $typeExtensions) + { + $this->typeExtensions = []; + foreach ($typeExtensions as $extension) { + if (!is_object($extension)) { + if (!isset($this->app[$extension])) { + throw new InvalidArgumentException(sprintf('Invalid form type extension. The silex service "%s" does not exist.', $extension)); + } + $extension = $this->app[$extension]; + } + $this->typeExtensions[$extension->getExtendedType()][] = $extension; + } + } + + private function setGuessers(array $guessers) + { + $this->guessers = []; + foreach ($guessers as $guesser) { + if (!is_object($guesser) && !isset($this->app[$guesser])) { + throw new InvalidArgumentException(sprintf('Invalid form type guesser. The silex service "%s" does not exist.', $guesser)); + } + $this->guessers[] = $guesser; + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/FormServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/FormServiceProvider.php new file mode 100644 index 00000000..00841d0b --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/FormServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Form\Extension\Csrf\CsrfExtension; +use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension; +use Symfony\Component\Form\Extension\Validator\ValidatorExtension as FormValidatorExtension; +use Symfony\Component\Form\FormFactory; +use Symfony\Component\Form\FormRegistry; +use Symfony\Component\Form\ResolvedFormTypeFactory; + +/** + * Symfony Form component Provider. + * + * @author Fabien Potencier + */ +class FormServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + if (!class_exists('Locale')) { + throw new \RuntimeException('You must either install the PHP intl extension or the Symfony Intl Component to use the Form extension.'); + } + + $app['form.types'] = function ($app) { + return array(); + }; + + $app['form.type.extensions'] = function ($app) { + return array(); + }; + + $app['form.type.guessers'] = function ($app) { + return array(); + }; + + $app['form.extension.csrf'] = function ($app) { + if (isset($app['translator'])) { + return new CsrfExtension($app['csrf.token_manager'], $app['translator']); + } + + return new CsrfExtension($app['csrf.token_manager']); + }; + + $app['form.extension.silex'] = function ($app) { + return new Form\SilexFormExtension($app, $app['form.types'], $app['form.type.extensions'], $app['form.type.guessers']); + }; + + $app['form.extensions'] = function ($app) { + $extensions = array( + new HttpFoundationExtension(), + ); + + if (isset($app['csrf.token_manager'])) { + $extensions[] = $app['form.extension.csrf']; + } + + if (isset($app['validator'])) { + $extensions[] = new FormValidatorExtension($app['validator']); + } + $extensions[] = $app['form.extension.silex']; + + return $extensions; + }; + + $app['form.factory'] = function ($app) { + return new FormFactory($app['form.registry'], $app['form.resolved_type_factory']); + }; + + $app['form.registry'] = function ($app) { + return new FormRegistry($app['form.extensions'], $app['form.resolved_type_factory']); + }; + + $app['form.resolved_type_factory'] = function ($app) { + return new ResolvedFormTypeFactory(); + }; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/HttpCache/HttpCache.php b/vendor/silex/silex/src/Silex/Provider/HttpCache/HttpCache.php new file mode 100644 index 00000000..b0ebb5cc --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/HttpCache/HttpCache.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 Silex\Provider\HttpCache; + +use Symfony\Component\HttpKernel\HttpCache\HttpCache as BaseHttpCache; +use Symfony\Component\HttpFoundation\Request; + +/** + * HTTP Cache extension to allow using the run() shortcut. + * + * @author Fabien Potencier + */ +class HttpCache extends BaseHttpCache +{ + /** + * Handles the Request and delivers the Response. + * + * @param Request $request The Request object + */ + public function run(Request $request = null) + { + if (null === $request) { + $request = Request::createFromGlobals(); + } + + $response = $this->handle($request); + $response->send(); + $this->terminate($request, $response); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/HttpCacheServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/HttpCacheServiceProvider.php new file mode 100644 index 00000000..8b3f37ee --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/HttpCacheServiceProvider.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Provider\HttpCache\HttpCache; +use Silex\Api\EventListenerProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpKernel\HttpCache\Store; +use Symfony\Component\HttpKernel\EventListener\SurrogateListener; + +/** + * Symfony HttpKernel component Provider for HTTP cache. + * + * @author Fabien Potencier + */ +class HttpCacheServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['http_cache'] = function ($app) { + $app['http_cache.options'] = array_replace( + array( + 'debug' => $app['debug'], + ), $app['http_cache.options'] + ); + + return new HttpCache($app, $app['http_cache.store'], $app['http_cache.esi'], $app['http_cache.options']); + }; + + $app['http_cache.esi'] = function ($app) { + return new Esi(); + }; + + $app['http_cache.store'] = function ($app) { + return new Store($app['http_cache.cache_dir']); + }; + + $app['http_cache.esi_listener'] = function ($app) { + return new SurrogateListener($app['http_cache.esi']); + }; + + $app['http_cache.options'] = array(); + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['http_cache.esi_listener']); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/HttpFragmentServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/HttpFragmentServiceProvider.php new file mode 100644 index 00000000..9a641f40 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/HttpFragmentServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; +use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer; +use Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer; +use Symfony\Component\HttpKernel\Fragment\HIncludeFragmentRenderer; +use Symfony\Component\HttpKernel\EventListener\FragmentListener; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\UriSigner; + +/** + * HttpKernel Fragment integration for Silex. + * + * @author Fabien Potencier + */ +class HttpFragmentServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['fragment.handler'] = function ($app) { + return new FragmentHandler($app['request_stack'], $app['fragment.renderers'], $app['debug']); + }; + + $app['fragment.renderer.inline'] = function ($app) { + $renderer = new InlineFragmentRenderer($app['kernel'], $app['dispatcher']); + $renderer->setFragmentPath($app['fragment.path']); + + return $renderer; + }; + + $app['fragment.renderer.hinclude'] = function ($app) { + $renderer = new HIncludeFragmentRenderer(null, $app['uri_signer'], $app['fragment.renderer.hinclude.global_template'], $app['charset']); + $renderer->setFragmentPath($app['fragment.path']); + + return $renderer; + }; + + $app['fragment.renderer.esi'] = function ($app) { + $renderer = new EsiFragmentRenderer($app['http_cache.esi'], $app['fragment.renderer.inline']); + $renderer->setFragmentPath($app['fragment.path']); + + return $renderer; + }; + + $app['fragment.listener'] = function ($app) { + return new FragmentListener($app['uri_signer'], $app['fragment.path']); + }; + + $app['uri_signer'] = function ($app) { + return new UriSigner($app['uri_signer.secret']); + }; + + $app['uri_signer.secret'] = md5(__DIR__); + $app['fragment.path'] = '/_fragment'; + $app['fragment.renderer.hinclude.global_template'] = null; + $app['fragment.renderers'] = function ($app) { + $renderers = array($app['fragment.renderer.inline'], $app['fragment.renderer.hinclude']); + + if (isset($app['http_cache.esi'])) { + $renderers[] = $app['fragment.renderer.esi']; + } + + return $renderers; + }; + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['fragment.listener']); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/HttpKernelServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/HttpKernelServiceProvider.php new file mode 100644 index 00000000..86f155fe --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/HttpKernelServiceProvider.php @@ -0,0 +1,101 @@ += 30100) { + return new SfControllerResolver($app['logger']); + } + + return new ControllerResolver($app, $app['logger']); + }; + + if (Kernel::VERSION_ID >= 30100) { + $app['argument_metadata_factory'] = function ($app) { + return new ArgumentMetadataFactory(); + }; + $app['argument_value_resolvers'] = function ($app) { + if (Kernel::VERSION_ID < 30200) { + return array( + new AppArgumentValueResolver($app), + new RequestAttributeValueResolver(), + new RequestValueResolver(), + new DefaultValueResolver(), + new VariadicValueResolver(), + ); + } + + return array_merge(array(new AppArgumentValueResolver($app)), ArgumentResolver::getDefaultArgumentValueResolvers()); + }; + } + + $app['argument_resolver'] = function ($app) { + if (Kernel::VERSION_ID >= 30100) { + return new ArgumentResolver($app['argument_metadata_factory'], $app['argument_value_resolvers']); + } + }; + + $app['kernel'] = function ($app) { + return new HttpKernel($app['dispatcher'], $app['resolver'], $app['request_stack'], $app['argument_resolver']); + }; + + $app['request_stack'] = function () { + return new RequestStack(); + }; + + $app['dispatcher'] = function () { + return new EventDispatcher(); + }; + + $app['callback_resolver'] = function ($app) { + return new CallbackResolver($app); + }; + } + + /** + * {@inheritdoc} + */ + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber(new ResponseListener($app['charset'])); + $dispatcher->addSubscriber(new MiddlewareListener($app)); + $dispatcher->addSubscriber(new ConverterListener($app['routes'], $app['callback_resolver'])); + $dispatcher->addSubscriber(new StringToResponseListener()); + + if (class_exists(HttpHeaderSerializer::class)) { + $dispatcher->addSubscriber(new AddLinkHeaderListener()); + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/LICENSE b/vendor/silex/silex/src/Silex/Provider/LICENSE new file mode 100644 index 00000000..bc6ad049 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-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/vendor/silex/silex/src/Silex/Provider/Locale/LocaleListener.php b/vendor/silex/silex/src/Silex/Provider/Locale/LocaleListener.php new file mode 100644 index 00000000..d5002640 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Locale/LocaleListener.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Locale; + +use Pimple\Container; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Initializes the locale based on the current request. + * + * @author Fabien Potencier + * @author Jérôme Tamarelle + */ +class LocaleListener implements EventSubscriberInterface +{ + private $app; + private $defaultLocale; + private $requestStack; + private $requestContext; + + public function __construct(Container $app, $defaultLocale = 'en', RequestStack $requestStack, RequestContext $requestContext = null) + { + $this->app = $app; + $this->defaultLocale = $defaultLocale; + $this->requestStack = $requestStack; + $this->requestContext = $requestContext; + } + + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + $request->setDefaultLocale($this->defaultLocale); + + $this->setLocale($request); + $this->setRouterContext($request); + + $this->app['locale'] = $request->getLocale(); + } + + public function onKernelFinishRequest(FinishRequestEvent $event) + { + if (null !== $parentRequest = $this->requestStack->getParentRequest()) { + $this->setRouterContext($parentRequest); + } + } + + private function setLocale(Request $request) + { + if ($locale = $request->attributes->get('_locale')) { + $request->setLocale($locale); + } + } + + private function setRouterContext(Request $request) + { + if (null !== $this->requestContext) { + $this->requestContext->setParameter('_locale', $request->getLocale()); + } + } + + public static function getSubscribedEvents() + { + return array( + // must be registered after the Router to have access to the _locale + KernelEvents::REQUEST => array(array('onKernelRequest', 16)), + KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + ); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/LocaleServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/LocaleServiceProvider.php new file mode 100644 index 00000000..ddea81bf --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/LocaleServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Silex\Provider\Locale\LocaleListener; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Locale Provider. + * + * @author Fabien Potencier + */ +class LocaleServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['locale.listener'] = function ($app) { + return new LocaleListener($app, $app['locale'], $app['request_stack'], isset($app['request_context']) ? $app['request_context'] : null); + }; + + $app['locale'] = 'en'; + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['locale.listener']); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/MonologServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/MonologServiceProvider.php new file mode 100644 index 00000000..f8cba4e2 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/MonologServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; +use Monolog\Handler; +use Monolog\ErrorHandler; +use Silex\Application; +use Silex\Api\BootableProviderInterface; +use Symfony\Bridge\Monolog\Handler\DebugHandler; +use Symfony\Bridge\Monolog\Handler\FingersCrossed\NotFoundActivationStrategy; +use Symfony\Bridge\Monolog\Processor\DebugProcessor; +use Silex\EventListener\LogListener; + +/** + * Monolog Provider. + * + * @author Fabien Potencier + */ +class MonologServiceProvider implements ServiceProviderInterface, BootableProviderInterface +{ + public function register(Container $app) + { + $app['logger'] = function () use ($app) { + return $app['monolog']; + }; + + if ($bridge = class_exists('Symfony\Bridge\Monolog\Logger')) { + $app['monolog.handler.debug'] = function () use ($app) { + $level = MonologServiceProvider::translateLevel($app['monolog.level']); + + return new DebugHandler($level); + }; + + if (isset($app['request_stack'])) { + $app['monolog.not_found_activation_strategy'] = function () use ($app) { + return new NotFoundActivationStrategy($app['request_stack'], array('^/'), $app['monolog.level']); + }; + } + } + + $app['monolog.logger.class'] = $bridge ? 'Symfony\Bridge\Monolog\Logger' : 'Monolog\Logger'; + + $app['monolog'] = function ($app) use ($bridge) { + $log = new $app['monolog.logger.class']($app['monolog.name']); + + $handler = new Handler\GroupHandler($app['monolog.handlers']); + if (isset($app['monolog.not_found_activation_strategy'])) { + $handler = new Handler\FingersCrossedHandler($handler, $app['monolog.not_found_activation_strategy']); + } + + $log->pushHandler($handler); + + if ($app['debug'] && $bridge) { + if (class_exists(DebugProcessor::class)) { + $log->pushProcessor(new DebugProcessor()); + } else { + $log->pushHandler($app['monolog.handler.debug']); + } + } + + return $log; + }; + + $app['monolog.formatter'] = function () { + return new LineFormatter(); + }; + + $app['monolog.handler'] = $defaultHandler = function () use ($app) { + $level = MonologServiceProvider::translateLevel($app['monolog.level']); + + $handler = new Handler\StreamHandler($app['monolog.logfile'], $level, $app['monolog.bubble'], $app['monolog.permission']); + $handler->setFormatter($app['monolog.formatter']); + + return $handler; + }; + + $app['monolog.handlers'] = function () use ($app, $defaultHandler) { + $handlers = array(); + + // enables the default handler if a logfile was set or the monolog.handler service was redefined + if ($app['monolog.logfile'] || $defaultHandler !== $app->raw('monolog.handler')) { + $handlers[] = $app['monolog.handler']; + } + + return $handlers; + }; + + $app['monolog.level'] = function () { + return Logger::DEBUG; + }; + + $app['monolog.listener'] = function () use ($app) { + return new LogListener($app['logger'], $app['monolog.exception.logger_filter']); + }; + + $app['monolog.name'] = 'app'; + $app['monolog.bubble'] = true; + $app['monolog.permission'] = null; + $app['monolog.exception.logger_filter'] = null; + $app['monolog.logfile'] = null; + $app['monolog.use_error_handler'] = function ($app) { + return !$app['debug']; + }; + } + + public function boot(Application $app) + { + if ($app['monolog.use_error_handler']) { + ErrorHandler::register($app['monolog']); + } + + if (isset($app['monolog.listener'])) { + $app['dispatcher']->addSubscriber($app['monolog.listener']); + } + } + + public static function translateLevel($name) + { + // level is already translated to logger constant, return as-is + if (is_int($name)) { + return $name; + } + + $levels = Logger::getLevels(); + $upper = strtoupper($name); + + if (!isset($levels[$upper])) { + throw new \InvalidArgumentException("Provided logging level '$name' does not exist. Must be a valid monolog logging level."); + } + + return $levels[$upper]; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/RememberMeServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/RememberMeServiceProvider.php new file mode 100644 index 00000000..766631c5 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/RememberMeServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Security\Core\Authentication\Provider\RememberMeAuthenticationProvider; +use Symfony\Component\Security\Http\Firewall\RememberMeListener; +use Symfony\Component\Security\Http\RememberMe\TokenBasedRememberMeServices; +use Symfony\Component\Security\Http\RememberMe\ResponseListener; + +/** + * Remember-me authentication for the SecurityServiceProvider. + * + * @author Jérôme Tamarelle + */ +class RememberMeServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['security.remember_me.response_listener'] = function ($app) { + if (!isset($app['security.token_storage'])) { + throw new \LogicException('You must register the SecurityServiceProvider to use the RememberMeServiceProvider'); + } + + return new ResponseListener(); + }; + + $app['security.authentication_listener.factory.remember_me'] = $app->protect(function ($name, $options) use ($app) { + if (empty($options['key'])) { + $options['key'] = $name; + } + + if (!isset($app['security.remember_me.service.'.$name])) { + $app['security.remember_me.service.'.$name] = $app['security.remember_me.service._proto']($name, $options); + } + + if (!isset($app['security.authentication_listener.'.$name.'.remember_me'])) { + $app['security.authentication_listener.'.$name.'.remember_me'] = $app['security.authentication_listener.remember_me._proto']($name, $options); + } + + if (!isset($app['security.authentication_provider.'.$name.'.remember_me'])) { + $app['security.authentication_provider.'.$name.'.remember_me'] = $app['security.authentication_provider.remember_me._proto']($name, $options); + } + + return array( + 'security.authentication_provider.'.$name.'.remember_me', + 'security.authentication_listener.'.$name.'.remember_me', + null, // entry point + 'remember_me', + ); + }); + + $app['security.remember_me.service._proto'] = $app->protect(function ($providerKey, $options) use ($app) { + return function () use ($providerKey, $options, $app) { + $options = array_replace(array( + 'name' => 'REMEMBERME', + 'lifetime' => 31536000, + 'path' => '/', + 'domain' => null, + 'secure' => false, + 'httponly' => true, + 'always_remember_me' => false, + 'remember_me_parameter' => '_remember_me', + ), $options); + + return new TokenBasedRememberMeServices(array($app['security.user_provider.'.$providerKey]), $options['key'], $providerKey, $options, $app['logger']); + }; + }); + + $app['security.authentication_listener.remember_me._proto'] = $app->protect(function ($providerKey) use ($app) { + return function () use ($app, $providerKey) { + $listener = new RememberMeListener( + $app['security.token_storage'], + $app['security.remember_me.service.'.$providerKey], + $app['security.authentication_manager'], + $app['logger'], + $app['dispatcher'] + ); + + return $listener; + }; + }); + + $app['security.authentication_provider.remember_me._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($app, $name, $options) { + return new RememberMeAuthenticationProvider($app['security.user_checker'], $options['key'], $name); + }; + }); + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['security.remember_me.response_listener']); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Routing/LazyRequestMatcher.php b/vendor/silex/silex/src/Silex/Provider/Routing/LazyRequestMatcher.php new file mode 100644 index 00000000..6837c79a --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Routing/LazyRequestMatcher.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 Silex\Provider\Routing; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; + +/** + * Implements a lazy UrlMatcher. + * + * @author Igor Wiedler + * @author Jérôme Tamarelle + */ +class LazyRequestMatcher implements RequestMatcherInterface +{ + private $factory; + + public function __construct(\Closure $factory) + { + $this->factory = $factory; + } + + /** + * Returns the corresponding RequestMatcherInterface instance. + * + * @return UrlMatcherInterface + */ + public function getRequestMatcher() + { + $matcher = call_user_func($this->factory); + if (!$matcher instanceof RequestMatcherInterface) { + throw new \LogicException("Factory supplied to LazyRequestMatcher must return implementation of Symfony\Component\Routing\RequestMatcherInterface."); + } + + return $matcher; + } + + /** + * {@inheritdoc} + */ + public function matchRequest(Request $request) + { + return $this->getRequestMatcher()->matchRequest($request); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Routing/RedirectableUrlMatcher.php b/vendor/silex/silex/src/Silex/Provider/Routing/RedirectableUrlMatcher.php new file mode 100644 index 00000000..d2fa39e8 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Routing/RedirectableUrlMatcher.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 Silex\Provider\Routing; + +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\Routing\Matcher\RedirectableUrlMatcher as BaseRedirectableUrlMatcher; + +/** + * Implements the RedirectableUrlMatcherInterface for Silex. + * + * @author Fabien Potencier + */ +class RedirectableUrlMatcher extends BaseRedirectableUrlMatcher +{ + /** + * {@inheritdoc} + */ + public function redirect($path, $route, $scheme = null) + { + $url = $this->context->getBaseUrl().$path; + $query = $this->context->getQueryString() ?: ''; + + if ($query !== '') { + $url .= '?'.$query; + } + + if ($this->context->getHost()) { + if ($scheme) { + $port = ''; + if ('http' === $scheme && 80 != $this->context->getHttpPort()) { + $port = ':'.$this->context->getHttpPort(); + } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) { + $port = ':'.$this->context->getHttpsPort(); + } + + $url = $scheme.'://'.$this->context->getHost().$port.$url; + } + } + + return array( + '_controller' => function ($url) { return new RedirectResponse($url, 301); }, + 'url' => $url, + ); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/RoutingServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/RoutingServiceProvider.php new file mode 100644 index 00000000..d040ba0d --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/RoutingServiceProvider.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\ControllerCollection; +use Silex\Api\EventListenerProviderInterface; +use Silex\Provider\Routing\RedirectableUrlMatcher; +use Silex\Provider\Routing\LazyRequestMatcher; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\HttpKernel\EventListener\RouterListener; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Symfony Routing component Provider. + * + * @author Fabien Potencier + */ +class RoutingServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['route_class'] = 'Silex\\Route'; + + $app['route_factory'] = $app->factory(function ($app) { + return new $app['route_class'](); + }); + + $app['routes_factory'] = $app->factory(function () { + return new RouteCollection(); + }); + + $app['routes'] = function ($app) { + return $app['routes_factory']; + }; + $app['url_generator'] = function ($app) { + return new UrlGenerator($app['routes'], $app['request_context']); + }; + + $app['request_matcher'] = function ($app) { + return new RedirectableUrlMatcher($app['routes'], $app['request_context']); + }; + + $app['request_context'] = function ($app) { + $context = new RequestContext(); + + $context->setHttpPort(isset($app['request.http_port']) ? $app['request.http_port'] : 80); + $context->setHttpsPort(isset($app['request.https_port']) ? $app['request.https_port'] : 443); + + return $context; + }; + + $app['controllers'] = function ($app) { + return $app['controllers_factory']; + }; + + $controllers_factory = function () use ($app, &$controllers_factory) { + return new ControllerCollection($app['route_factory'], $app['routes_factory'], $controllers_factory); + }; + $app['controllers_factory'] = $app->factory($controllers_factory); + + $app['routing.listener'] = function ($app) { + $urlMatcher = new LazyRequestMatcher(function () use ($app) { + return $app['request_matcher']; + }); + + return new RouterListener($urlMatcher, $app['request_stack'], $app['request_context'], $app['logger']); + }; + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['routing.listener']); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/SecurityServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/SecurityServiceProvider.php new file mode 100644 index 00000000..ebc5bea6 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SecurityServiceProvider.php @@ -0,0 +1,696 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Application; +use Silex\Api\BootableProviderInterface; +use Silex\Api\ControllerProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\RequestMatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Core\User\UserChecker; +use Symfony\Component\Security\Core\User\InMemoryUserProvider; +use Symfony\Component\Security\Core\Encoder\EncoderFactory; +use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; +use Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder; +use Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder; +use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider; +use Symfony\Component\Security\Core\Authentication\Provider\AnonymousAuthenticationProvider; +use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; +use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler; +use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; +use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; +use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter; +use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; +use Symfony\Component\Security\Core\Role\RoleHierarchy; +use Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator; +use Symfony\Component\Security\Http\Firewall; +use Symfony\Component\Security\Http\FirewallMap; +use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener; +use Symfony\Component\Security\Http\Firewall\AccessListener; +use Symfony\Component\Security\Http\Firewall\BasicAuthenticationListener; +use Symfony\Component\Security\Http\Firewall\LogoutListener; +use Symfony\Component\Security\Http\Firewall\SwitchUserListener; +use Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener; +use Symfony\Component\Security\Http\Firewall\ContextListener; +use Symfony\Component\Security\Http\Firewall\ExceptionListener; +use Symfony\Component\Security\Http\Firewall\ChannelListener; +use Symfony\Component\Security\Http\EntryPoint\FormAuthenticationEntryPoint; +use Symfony\Component\Security\Http\EntryPoint\BasicAuthenticationEntryPoint; +use Symfony\Component\Security\Http\EntryPoint\RetryAuthenticationEntryPoint; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy; +use Symfony\Component\Security\Http\Logout\SessionLogoutHandler; +use Symfony\Component\Security\Http\Logout\DefaultLogoutSuccessHandler; +use Symfony\Component\Security\Http\AccessMap; +use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; +use Symfony\Component\Security\Guard\Firewall\GuardAuthenticationListener; +use Symfony\Component\Security\Guard\Provider\GuardAuthenticationProvider; + +/** + * Symfony Security component Provider. + * + * @author Fabien Potencier + */ +class SecurityServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface, ControllerProviderInterface, BootableProviderInterface +{ + protected $fakeRoutes; + + public function register(Container $app) + { + // used to register routes for login_check and logout + $this->fakeRoutes = array(); + + $that = $this; + + $app['security.role_hierarchy'] = array(); + $app['security.access_rules'] = array(); + $app['security.hide_user_not_found'] = true; + $app['security.encoder.bcrypt.cost'] = 13; + + $app['security.authorization_checker'] = function ($app) { + return new AuthorizationChecker($app['security.token_storage'], $app['security.authentication_manager'], $app['security.access_manager']); + }; + + $app['security.token_storage'] = function ($app) { + return new TokenStorage(); + }; + + $app['user'] = $app->factory(function ($app) { + if (null === $token = $app['security.token_storage']->getToken()) { + return; + } + + if (!is_object($user = $token->getUser())) { + return; + } + + return $user; + }); + + $app['security.authentication_manager'] = function ($app) { + $manager = new AuthenticationProviderManager($app['security.authentication_providers']); + $manager->setEventDispatcher($app['dispatcher']); + + return $manager; + }; + + // by default, all users use the digest encoder + $app['security.encoder_factory'] = function ($app) { + return new EncoderFactory(array( + 'Symfony\Component\Security\Core\User\UserInterface' => $app['security.default_encoder'], + )); + }; + + // by default, all users use the BCrypt encoder + $app['security.default_encoder'] = function ($app) { + return $app['security.encoder.bcrypt']; + }; + + $app['security.encoder.digest'] = function ($app) { + return new MessageDigestPasswordEncoder(); + }; + + $app['security.encoder.bcrypt'] = function ($app) { + return new BCryptPasswordEncoder($app['security.encoder.bcrypt.cost']); + }; + + $app['security.encoder.pbkdf2'] = function ($app) { + return new Pbkdf2PasswordEncoder(); + }; + + $app['security.user_checker'] = function ($app) { + return new UserChecker(); + }; + + $app['security.access_manager'] = function ($app) { + return new AccessDecisionManager($app['security.voters']); + }; + + $app['security.voters'] = function ($app) { + return array( + new RoleHierarchyVoter(new RoleHierarchy($app['security.role_hierarchy'])), + new AuthenticatedVoter($app['security.trust_resolver']), + ); + }; + + $app['security.firewall'] = function ($app) { + return new Firewall($app['security.firewall_map'], $app['dispatcher']); + }; + + $app['security.channel_listener'] = function ($app) { + return new ChannelListener( + $app['security.access_map'], + new RetryAuthenticationEntryPoint( + isset($app['request.http_port']) ? $app['request.http_port'] : 80, + isset($app['request.https_port']) ? $app['request.https_port'] : 443 + ), + $app['logger'] + ); + }; + + // generate the build-in authentication factories + foreach (array('logout', 'pre_auth', 'guard', 'form', 'http', 'remember_me', 'anonymous') as $type) { + $entryPoint = null; + if ('http' === $type) { + $entryPoint = 'http'; + } elseif ('form' === $type) { + $entryPoint = 'form'; + } elseif ('guard' === $type) { + $entryPoint = 'guard'; + } + + $app['security.authentication_listener.factory.'.$type] = $app->protect(function ($name, $options) use ($type, $app, $entryPoint) { + if ($entryPoint && !isset($app['security.entry_point.'.$name.'.'.$entryPoint])) { + $app['security.entry_point.'.$name.'.'.$entryPoint] = $app['security.entry_point.'.$entryPoint.'._proto']($name, $options); + } + + if (!isset($app['security.authentication_listener.'.$name.'.'.$type])) { + $app['security.authentication_listener.'.$name.'.'.$type] = $app['security.authentication_listener.'.$type.'._proto']($name, $options); + } + + $provider = 'dao'; + if ('anonymous' === $type) { + $provider = 'anonymous'; + } elseif ('guard' === $type) { + $provider = 'guard'; + } + if (!isset($app['security.authentication_provider.'.$name.'.'.$provider])) { + $app['security.authentication_provider.'.$name.'.'.$provider] = $app['security.authentication_provider.'.$provider.'._proto']($name, $options); + } + + return array( + 'security.authentication_provider.'.$name.'.'.$provider, + 'security.authentication_listener.'.$name.'.'.$type, + $entryPoint ? 'security.entry_point.'.$name.'.'.$entryPoint : null, + $type, + ); + }); + } + + $app['security.firewall_map'] = function ($app) { + $positions = array('logout', 'pre_auth', 'guard', 'form', 'http', 'remember_me', 'anonymous'); + $providers = array(); + $configs = array(); + foreach ($app['security.firewalls'] as $name => $firewall) { + $entryPoint = null; + $pattern = isset($firewall['pattern']) ? $firewall['pattern'] : null; + $users = isset($firewall['users']) ? $firewall['users'] : array(); + $security = isset($firewall['security']) ? (bool) $firewall['security'] : true; + $stateless = isset($firewall['stateless']) ? (bool) $firewall['stateless'] : false; + $context = isset($firewall['context']) ? $firewall['context'] : $name; + $hosts = isset($firewall['hosts']) ? $firewall['hosts'] : null; + $methods = isset($firewall['methods']) ? $firewall['methods'] : null; + unset($firewall['pattern'], $firewall['users'], $firewall['security'], $firewall['stateless'], $firewall['context'], $firewall['methods'], $firewall['hosts']); + $protected = false === $security ? false : count($firewall); + $listeners = array('security.channel_listener'); + + if ($protected) { + if (!isset($app['security.context_listener.'.$name])) { + if (!isset($app['security.user_provider.'.$name])) { + $app['security.user_provider.'.$name] = is_array($users) ? $app['security.user_provider.inmemory._proto']($users) : $users; + } + + $app['security.context_listener.'.$name] = $app['security.context_listener._proto']($name, array($app['security.user_provider.'.$name])); + } + + if (false === $stateless) { + $listeners[] = 'security.context_listener.'.$context; + } + + $factories = array(); + foreach ($positions as $position) { + $factories[$position] = array(); + } + + foreach ($firewall as $type => $options) { + if ('switch_user' === $type) { + continue; + } + + // normalize options + if (!is_array($options)) { + if (!$options) { + continue; + } + + $options = array(); + } + + if (!isset($app['security.authentication_listener.factory.'.$type])) { + throw new \LogicException(sprintf('The "%s" authentication entry is not registered.', $type)); + } + + $options['stateless'] = $stateless; + + list($providerId, $listenerId, $entryPointId, $position) = $app['security.authentication_listener.factory.'.$type]($name, $options); + + if (null !== $entryPointId) { + $entryPoint = $entryPointId; + } + + $factories[$position][] = $listenerId; + $providers[] = $providerId; + } + + foreach ($positions as $position) { + foreach ($factories[$position] as $listener) { + $listeners[] = $listener; + } + } + + $listeners[] = 'security.access_listener'; + + if (isset($firewall['switch_user'])) { + $app['security.switch_user.'.$name] = $app['security.authentication_listener.switch_user._proto']($name, $firewall['switch_user']); + + $listeners[] = 'security.switch_user.'.$name; + } + + if (!isset($app['security.exception_listener.'.$name])) { + if (null == $entryPoint) { + $app[$entryPoint = 'security.entry_point.'.$name.'.form'] = $app['security.entry_point.form._proto']($name, array()); + } + $accessDeniedHandler = null; + if (isset($app['security.access_denied_handler.'.$name])) { + $accessDeniedHandler = $app['security.access_denied_handler.'.$name]; + } + $app['security.exception_listener.'.$name] = $app['security.exception_listener._proto']($entryPoint, $name, $accessDeniedHandler); + } + } + + $configs[$name] = array( + 'pattern' => $pattern, + 'listeners' => $listeners, + 'protected' => $protected, + 'methods' => $methods, + 'hosts' => $hosts, + ); + } + + $app['security.authentication_providers'] = array_map(function ($provider) use ($app) { + return $app[$provider]; + }, array_unique($providers)); + + $map = new FirewallMap(); + foreach ($configs as $name => $config) { + if (is_string($config['pattern'])) { + $requestMatcher = new RequestMatcher($config['pattern'], $config['hosts'], $config['methods']); + } else { + $requestMatcher = $config['pattern']; + } + + $map->add( + $requestMatcher, + array_map(function ($listenerId) use ($app, $name) { + $listener = $app[$listenerId]; + + if (isset($app['security.remember_me.service.'.$name])) { + if ($listener instanceof AbstractAuthenticationListener || $listener instanceof GuardAuthenticationListener) { + $listener->setRememberMeServices($app['security.remember_me.service.'.$name]); + } + if ($listener instanceof LogoutListener) { + $listener->addHandler($app['security.remember_me.service.'.$name]); + } + } + + return $listener; + }, $config['listeners']), + $config['protected'] ? $app['security.exception_listener.'.$name] : null + ); + } + + return $map; + }; + + $app['security.access_listener'] = function ($app) { + return new AccessListener( + $app['security.token_storage'], + $app['security.access_manager'], + $app['security.access_map'], + $app['security.authentication_manager'], + $app['logger'] + ); + }; + + $app['security.access_map'] = function ($app) { + $map = new AccessMap(); + + foreach ($app['security.access_rules'] as $rule) { + if (is_string($rule[0])) { + $rule[0] = new RequestMatcher($rule[0]); + } elseif (is_array($rule[0])) { + $rule[0] += array( + 'path' => null, + 'host' => null, + 'methods' => null, + 'ips' => null, + 'attributes' => array(), + 'schemes' => null, + ); + $rule[0] = new RequestMatcher($rule[0]['path'], $rule[0]['host'], $rule[0]['methods'], $rule[0]['ips'], $rule[0]['attributes'], $rule[0]['schemes']); + } + $map->add($rule[0], (array) $rule[1], isset($rule[2]) ? $rule[2] : null); + } + + return $map; + }; + + $app['security.trust_resolver'] = function ($app) { + return new AuthenticationTrustResolver('Symfony\Component\Security\Core\Authentication\Token\AnonymousToken', 'Symfony\Component\Security\Core\Authentication\Token\RememberMeToken'); + }; + + $app['security.session_strategy'] = function ($app) { + return new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE); + }; + + $app['security.http_utils'] = function ($app) { + return new HttpUtils($app['url_generator'], $app['request_matcher']); + }; + + $app['security.last_error'] = $app->protect(function (Request $request) { + if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { + return $request->attributes->get(Security::AUTHENTICATION_ERROR)->getMessage(); + } + + $session = $request->getSession(); + if ($session && $session->has(Security::AUTHENTICATION_ERROR)) { + $message = $session->get(Security::AUTHENTICATION_ERROR)->getMessage(); + $session->remove(Security::AUTHENTICATION_ERROR); + + return $message; + } + }); + + // prototypes (used by the Firewall Map) + + $app['security.context_listener._proto'] = $app->protect(function ($providerKey, $userProviders) use ($app) { + return function () use ($app, $userProviders, $providerKey) { + return new ContextListener( + $app['security.token_storage'], + $userProviders, + $providerKey, + $app['logger'], + $app['dispatcher'] + ); + }; + }); + + $app['security.user_provider.inmemory._proto'] = $app->protect(function ($params) use ($app) { + return function () use ($app, $params) { + $users = array(); + foreach ($params as $name => $user) { + $users[$name] = array('roles' => (array) $user[0], 'password' => $user[1]); + } + + return new InMemoryUserProvider($users); + }; + }); + + $app['security.exception_listener._proto'] = $app->protect(function ($entryPoint, $name, $accessDeniedHandler = null) use ($app) { + return function () use ($app, $entryPoint, $name, $accessDeniedHandler) { + return new ExceptionListener( + $app['security.token_storage'], + $app['security.trust_resolver'], + $app['security.http_utils'], + $name, + $app[$entryPoint], + null, // errorPage + $accessDeniedHandler, + $app['logger'] + ); + }; + }); + + $app['security.authentication.success_handler._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($name, $options, $app) { + $handler = new DefaultAuthenticationSuccessHandler( + $app['security.http_utils'], + $options + ); + $handler->setProviderKey($name); + + return $handler; + }; + }); + + $app['security.authentication.failure_handler._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($name, $options, $app) { + return new DefaultAuthenticationFailureHandler( + $app, + $app['security.http_utils'], + $options, + $app['logger'] + ); + }; + }); + + $app['security.authentication_listener.guard._proto'] = $app->protect(function ($providerKey, $options) use ($app, $that) { + return function () use ($app, $providerKey, $options, $that) { + if (!isset($app['security.authentication.guard_handler'])) { + $app['security.authentication.guard_handler'] = new GuardAuthenticatorHandler($app['security.token_storage'], $app['dispatcher']); + } + + $authenticators = array(); + foreach ($options['authenticators'] as $authenticatorId) { + $authenticators[] = $app[$authenticatorId]; + } + + return new GuardAuthenticationListener( + $app['security.authentication.guard_handler'], + $app['security.authentication_manager'], + $providerKey, + $authenticators, + $app['logger'] + ); + }; + }); + + $app['security.authentication_listener.form._proto'] = $app->protect(function ($name, $options) use ($app, $that) { + return function () use ($app, $name, $options, $that) { + $that->addFakeRoute( + 'match', + $tmp = isset($options['check_path']) ? $options['check_path'] : '/login_check', + str_replace('/', '_', ltrim($tmp, '/')) + ); + + $class = isset($options['listener_class']) ? $options['listener_class'] : 'Symfony\\Component\\Security\\Http\\Firewall\\UsernamePasswordFormAuthenticationListener'; + + if (!isset($app['security.authentication.success_handler.'.$name])) { + $app['security.authentication.success_handler.'.$name] = $app['security.authentication.success_handler._proto']($name, $options); + } + + if (!isset($app['security.authentication.failure_handler.'.$name])) { + $app['security.authentication.failure_handler.'.$name] = $app['security.authentication.failure_handler._proto']($name, $options); + } + + return new $class( + $app['security.token_storage'], + $app['security.authentication_manager'], + isset($app['security.session_strategy.'.$name]) ? $app['security.session_strategy.'.$name] : $app['security.session_strategy'], + $app['security.http_utils'], + $name, + $app['security.authentication.success_handler.'.$name], + $app['security.authentication.failure_handler.'.$name], + $options, + $app['logger'], + $app['dispatcher'], + isset($options['with_csrf']) && $options['with_csrf'] && isset($app['csrf.token_manager']) ? $app['csrf.token_manager'] : null + ); + }; + }); + + $app['security.authentication_listener.http._proto'] = $app->protect(function ($providerKey, $options) use ($app) { + return function () use ($app, $providerKey, $options) { + return new BasicAuthenticationListener( + $app['security.token_storage'], + $app['security.authentication_manager'], + $providerKey, + $app['security.entry_point.'.$providerKey.'.http'], + $app['logger'] + ); + }; + }); + + $app['security.authentication_listener.anonymous._proto'] = $app->protect(function ($providerKey, $options) use ($app) { + return function () use ($app, $providerKey, $options) { + return new AnonymousAuthenticationListener( + $app['security.token_storage'], + $providerKey, + $app['logger'] + ); + }; + }); + + $app['security.authentication.logout_handler._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($name, $options, $app) { + return new DefaultLogoutSuccessHandler( + $app['security.http_utils'], + isset($options['target_url']) ? $options['target_url'] : '/' + ); + }; + }); + + $app['security.authentication_listener.logout._proto'] = $app->protect(function ($name, $options) use ($app, $that) { + return function () use ($app, $name, $options, $that) { + $that->addFakeRoute( + 'get', + $tmp = isset($options['logout_path']) ? $options['logout_path'] : '/logout', + str_replace('/', '_', ltrim($tmp, '/')) + ); + + if (!isset($app['security.authentication.logout_handler.'.$name])) { + $app['security.authentication.logout_handler.'.$name] = $app['security.authentication.logout_handler._proto']($name, $options); + } + + $listener = new LogoutListener( + $app['security.token_storage'], + $app['security.http_utils'], + $app['security.authentication.logout_handler.'.$name], + $options, + isset($options['with_csrf']) && $options['with_csrf'] && isset($app['csrf.token_manager']) ? $app['csrf.token_manager'] : null + ); + + $invalidateSession = isset($options['invalidate_session']) ? $options['invalidate_session'] : true; + if (true === $invalidateSession && false === $options['stateless']) { + $listener->addHandler(new SessionLogoutHandler()); + } + + return $listener; + }; + }); + + $app['security.authentication_listener.switch_user._proto'] = $app->protect(function ($name, $options) use ($app, $that) { + return function () use ($app, $name, $options, $that) { + return new SwitchUserListener( + $app['security.token_storage'], + $app['security.user_provider.'.$name], + $app['security.user_checker'], + $name, + $app['security.access_manager'], + $app['logger'], + isset($options['parameter']) ? $options['parameter'] : '_switch_user', + isset($options['role']) ? $options['role'] : 'ROLE_ALLOWED_TO_SWITCH', + $app['dispatcher'] + ); + }; + }); + + $app['security.entry_point.form._proto'] = $app->protect(function ($name, array $options) use ($app) { + return function () use ($app, $options) { + $loginPath = isset($options['login_path']) ? $options['login_path'] : '/login'; + $useForward = isset($options['use_forward']) ? $options['use_forward'] : false; + + return new FormAuthenticationEntryPoint($app, $app['security.http_utils'], $loginPath, $useForward); + }; + }); + + $app['security.entry_point.http._proto'] = $app->protect(function ($name, array $options) use ($app) { + return function () use ($app, $name, $options) { + return new BasicAuthenticationEntryPoint(isset($options['real_name']) ? $options['real_name'] : 'Secured'); + }; + }); + + $app['security.entry_point.guard._proto'] = $app->protect(function ($name, array $options) use ($app) { + if (isset($options['entry_point'])) { + // if it's configured explicitly, use it! + return $app[$options['entry_point']]; + } + $authenticatorIds = $options['authenticators']; + if (count($authenticatorIds) == 1) { + // if there is only one authenticator, use that as the entry point + return $app[reset($authenticatorIds)]; + } + // we have multiple entry points - we must ask them to configure one + throw new \LogicException(sprintf( + 'Because you have multiple guard configurators, you need to set the "guard.entry_point" key to one of your configurators (%s)', + implode(', ', $authenticatorIds) + )); + }); + + $app['security.authentication_provider.dao._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($app, $name) { + return new DaoAuthenticationProvider( + $app['security.user_provider.'.$name], + $app['security.user_checker'], + $name, + $app['security.encoder_factory'], + $app['security.hide_user_not_found'] + ); + }; + }); + + $app['security.authentication_provider.guard._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($app, $name, $options) { + $authenticators = array(); + foreach ($options['authenticators'] as $authenticatorId) { + $authenticators[] = $app[$authenticatorId]; + } + + return new GuardAuthenticationProvider( + $authenticators, + $app['security.user_provider.'.$name], + $name, + $app['security.user_checker'] + ); + }; + }); + + $app['security.authentication_provider.anonymous._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($app, $name) { + return new AnonymousAuthenticationProvider($name); + }; + }); + + if (isset($app['validator'])) { + $app['security.validator.user_password_validator'] = function ($app) { + return new UserPasswordValidator($app['security.token_storage'], $app['security.encoder_factory']); + }; + + $app['validator.validator_service_ids'] = array_merge($app['validator.validator_service_ids'], array('security.validator.user_password' => 'security.validator.user_password_validator')); + } + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['security.firewall']); + } + + public function connect(Application $app) + { + $controllers = $app['controllers_factory']; + foreach ($this->fakeRoutes as $route) { + list($method, $pattern, $name) = $route; + + $controllers->$method($pattern)->run(null)->bind($name); + } + + return $controllers; + } + + public function boot(Application $app) + { + $app->mount('/', $this->connect($app)); + } + + public function addFakeRoute($method, $pattern, $name) + { + $this->fakeRoutes[] = array($method, $pattern, $name); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/SerializerServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/SerializerServiceProvider.php new file mode 100644 index 00000000..8986abef --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SerializerServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Encoder\XmlEncoder; +use Symfony\Component\Serializer\Normalizer\CustomNormalizer; +use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; + +/** + * Symfony Serializer component Provider. + * + * @author Fabien Potencier + * @author Marijn Huizendveld + */ +class SerializerServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc} + * + * This method registers a serializer service. {@link http://api.symfony.com/master/Symfony/Component/Serializer/Serializer.html + * The service is provided by the Symfony Serializer component}. + */ + public function register(Container $app) + { + $app['serializer'] = function ($app) { + return new Serializer($app['serializer.normalizers'], $app['serializer.encoders']); + }; + + $app['serializer.encoders'] = function () { + return array(new JsonEncoder(), new XmlEncoder()); + }; + + $app['serializer.normalizers'] = function () { + return array(new CustomNormalizer(), new GetSetMethodNormalizer()); + }; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/ServiceControllerServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/ServiceControllerServiceProvider.php new file mode 100644 index 00000000..1c38adc9 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/ServiceControllerServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\ServiceControllerResolver; + +class ServiceControllerServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app->extend('resolver', function ($resolver, $app) { + return new ServiceControllerResolver($resolver, $app['callback_resolver']); + }); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Session/SessionListener.php b/vendor/silex/silex/src/Silex/Provider/Session/SessionListener.php new file mode 100644 index 00000000..aba4c4e8 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Session/SessionListener.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 Silex\Provider\Session; + +use Pimple\Container; +use Symfony\Component\HttpKernel\EventListener\SessionListener as BaseSessionListener; + +/** + * Sets the session in the request. + * + * @author Fabien Potencier + */ +class SessionListener extends BaseSessionListener +{ + private $app; + + public function __construct(Container $app) + { + $this->app = $app; + } + + protected function getSession() + { + if (!isset($this->app['session'])) { + return; + } + + return $this->app['session']; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Session/TestSessionListener.php b/vendor/silex/silex/src/Silex/Provider/Session/TestSessionListener.php new file mode 100644 index 00000000..ab98eb12 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Session/TestSessionListener.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 Silex\Provider\Session; + +use Pimple\Container; +use Symfony\Component\HttpKernel\EventListener\TestSessionListener as BaseTestSessionListener; + +/** + * Simulates sessions for testing purpose. + * + * @author Fabien Potencier + */ +class TestSessionListener extends BaseTestSessionListener +{ + private $app; + + public function __construct(Container $app) + { + $this->app = $app; + } + + protected function getSession() + { + if (!isset($this->app['session'])) { + return; + } + + return $this->app['session']; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/SessionServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/SessionServiceProvider.php new file mode 100644 index 00000000..a51e230e --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SessionServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Silex\Provider\Session\SessionListener; +use Silex\Provider\Session\TestSessionListener; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage; +use Symfony\Component\HttpFoundation\Session\Session; + +/** + * Symfony HttpFoundation component Provider for sessions. + * + * @author Fabien Potencier + */ +class SessionServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['session.test'] = false; + + $app['session'] = function ($app) { + return new Session($app['session.storage'], $app['session.attribute_bag'], $app['session.flash_bag']); + }; + + $app['session.storage'] = function ($app) { + if ($app['session.test']) { + return $app['session.storage.test']; + } + + return $app['session.storage.native']; + }; + + $app['session.storage.handler'] = function ($app) { + return new NativeFileSessionHandler($app['session.storage.save_path']); + }; + + $app['session.storage.native'] = function ($app) { + return new NativeSessionStorage( + $app['session.storage.options'], + $app['session.storage.handler'] + ); + }; + + $app['session.listener'] = function ($app) { + return new SessionListener($app); + }; + + $app['session.storage.test'] = function () { + return new MockFileSessionStorage(); + }; + + $app['session.listener.test'] = function ($app) { + return new TestSessionListener($app); + }; + + $app['session.storage.options'] = array(); + $app['session.default_locale'] = 'en'; + $app['session.storage.save_path'] = null; + $app['session.attribute_bag'] = null; + $app['session.flash_bag'] = null; + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['session.listener']); + + if ($app['session.test']) { + $app['dispatcher']->addSubscriber($app['session.listener.test']); + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/SwiftmailerServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/SwiftmailerServiceProvider.php new file mode 100644 index 00000000..c3dce6ce --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SwiftmailerServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; + +/** + * Swiftmailer Provider. + * + * @author Fabien Potencier + */ +class SwiftmailerServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['swiftmailer.options'] = array(); + $app['swiftmailer.use_spool'] = true; + + $app['mailer.initialized'] = false; + + $app['mailer'] = function ($app) { + $app['mailer.initialized'] = true; + $transport = $app['swiftmailer.use_spool'] ? $app['swiftmailer.spooltransport'] : $app['swiftmailer.transport']; + + return new \Swift_Mailer($transport); + }; + + $app['swiftmailer.spooltransport'] = function ($app) { + return new \Swift_Transport_SpoolTransport($app['swiftmailer.transport.eventdispatcher'], $app['swiftmailer.spool']); + }; + + $app['swiftmailer.spool'] = function ($app) { + return new \Swift_MemorySpool(); + }; + + $app['swiftmailer.transport'] = function ($app) { + $transport = new \Swift_Transport_EsmtpTransport( + $app['swiftmailer.transport.buffer'], + array($app['swiftmailer.transport.authhandler']), + $app['swiftmailer.transport.eventdispatcher'] + ); + + $options = $app['swiftmailer.options'] = array_replace(array( + 'host' => 'localhost', + 'port' => 25, + 'username' => '', + 'password' => '', + 'encryption' => null, + 'auth_mode' => null, + ), $app['swiftmailer.options']); + + $transport->setHost($options['host']); + $transport->setPort($options['port']); + $transport->setEncryption($options['encryption']); + $transport->setUsername($options['username']); + $transport->setPassword($options['password']); + $transport->setAuthMode($options['auth_mode']); + + return $transport; + }; + + $app['swiftmailer.transport.buffer'] = function () { + return new \Swift_Transport_StreamBuffer(new \Swift_StreamFilters_StringReplacementFilterFactory()); + }; + + $app['swiftmailer.transport.authhandler'] = function () { + return new \Swift_Transport_Esmtp_AuthHandler(array( + new \Swift_Transport_Esmtp_Auth_CramMd5Authenticator(), + new \Swift_Transport_Esmtp_Auth_LoginAuthenticator(), + new \Swift_Transport_Esmtp_Auth_PlainAuthenticator(), + )); + }; + + $app['swiftmailer.transport.eventdispatcher'] = function ($app) { + $dispatcher = new \Swift_Events_SimpleEventDispatcher(); + + $plugins = $app['swiftmailer.plugins']; + + if (null !== $app['swiftmailer.sender_address']) { + $plugins[] = new \Swift_Plugins_ImpersonatePlugin($app['swiftmailer.sender_address']); + } + + if (!empty($app['swiftmailer.delivery_addresses'])) { + $plugins[] = new \Swift_Plugins_RedirectingPlugin( + $app['swiftmailer.delivery_addresses'], + $app['swiftmailer.delivery_whitelist'] + ); + } + + foreach ($plugins as $plugin) { + $dispatcher->bindEventListener($plugin); + } + + return $dispatcher; + }; + + $app['swiftmailer.plugins'] = function ($app) { + return array(); + }; + + $app['swiftmailer.sender_address'] = null; + $app['swiftmailer.delivery_addresses'] = array(); + $app['swiftmailer.delivery_whitelist'] = array(); + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + // Event has no typehint as it can be either a PostResponseEvent or a ConsoleTerminateEvent + $onTerminate = function ($event) use ($app) { + // To speed things up (by avoiding Swift Mailer initialization), flush + // messages only if our mailer has been created (potentially used) + if ($app['mailer.initialized'] && $app['swiftmailer.use_spool'] && $app['swiftmailer.spooltransport'] instanceof \Swift_Transport_SpoolTransport) { + $app['swiftmailer.spooltransport']->getSpool()->flushQueue($app['swiftmailer.transport']); + } + }; + + $dispatcher->addListener(KernelEvents::TERMINATE, $onTerminate); + + if (class_exists('Symfony\Component\Console\ConsoleEvents')) { + $dispatcher->addListener(ConsoleEvents::TERMINATE, $onTerminate); + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/TranslationServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/TranslationServiceProvider.php new file mode 100644 index 00000000..0b8a8954 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/TranslationServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Translation\Translator; +use Symfony\Component\Translation\MessageSelector; +use Symfony\Component\Translation\Loader\ArrayLoader; +use Symfony\Component\Translation\Loader\XliffFileLoader; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\EventListener\TranslatorListener; +use Silex\Api\EventListenerProviderInterface; + +/** + * Symfony Translation component Provider. + * + * @author Fabien Potencier + */ +class TranslationServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['translator'] = function ($app) { + if (!isset($app['locale'])) { + throw new \LogicException('You must define \'locale\' parameter or register the LocaleServiceProvider to use the TranslationServiceProvider'); + } + + $translator = new Translator($app['locale'], $app['translator.message_selector'], $app['translator.cache_dir'], $app['debug']); + $translator->setFallbackLocales($app['locale_fallbacks']); + $translator->addLoader('array', new ArrayLoader()); + $translator->addLoader('xliff', new XliffFileLoader()); + + if (isset($app['validator'])) { + $r = new \ReflectionClass('Symfony\Component\Validator\Validation'); + $file = dirname($r->getFilename()).'/Resources/translations/validators.'.$app['locale'].'.xlf'; + if (file_exists($file)) { + $translator->addResource('xliff', $file, $app['locale'], 'validators'); + } + } + + if (isset($app['form.factory'])) { + $r = new \ReflectionClass('Symfony\Component\Form\Form'); + $file = dirname($r->getFilename()).'/Resources/translations/validators.'.$app['locale'].'.xlf'; + if (file_exists($file)) { + $translator->addResource('xliff', $file, $app['locale'], 'validators'); + } + } + + // Register default resources + foreach ($app['translator.resources'] as $resource) { + $translator->addResource($resource[0], $resource[1], $resource[2], $resource[3]); + } + + foreach ($app['translator.domains'] as $domain => $data) { + foreach ($data as $locale => $messages) { + $translator->addResource('array', $messages, $locale, $domain); + } + } + + return $translator; + }; + + if (isset($app['request_stack'])) { + $app['translator.listener'] = function ($app) { + return new TranslatorListener($app['translator'], $app['request_stack']); + }; + } + + $app['translator.message_selector'] = function () { + return new MessageSelector(); + }; + + $app['translator.resources'] = $app->protect(function ($app) { + return array(); + }); + + $app['translator.domains'] = array(); + $app['locale_fallbacks'] = array('en'); + $app['translator.cache_dir'] = null; + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + if (isset($app['translator.listener'])) { + $dispatcher->addSubscriber($app['translator.listener']); + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Twig/RuntimeLoader.php b/vendor/silex/silex/src/Silex/Provider/Twig/RuntimeLoader.php new file mode 100644 index 00000000..9a7aa915 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Twig/RuntimeLoader.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 Silex\Provider\Twig; + +use Pimple\Container; + +/** + * Loads Twig extension runtimes via Pimple. + * + * @author Fabien Potencier + */ +class RuntimeLoader implements \Twig_RuntimeLoaderInterface +{ + private $container; + private $mapping; + + public function __construct(Container $container, array $mapping) + { + $this->container = $container; + $this->mapping = $mapping; + } + + /** + * {@inheritdoc} + */ + public function load($class) + { + if (isset($this->mapping[$class])) { + return $this->container[$this->mapping[$class]]; + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/TwigServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/TwigServiceProvider.php new file mode 100644 index 00000000..f15a93bf --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/TwigServiceProvider.php @@ -0,0 +1,190 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Provider\Twig\RuntimeLoader; +use Symfony\Bridge\Twig\AppVariable; +use Symfony\Bridge\Twig\Extension\AssetExtension; +use Symfony\Bridge\Twig\Extension\DumpExtension; +use Symfony\Bridge\Twig\Extension\RoutingExtension; +use Symfony\Bridge\Twig\Extension\TranslationExtension; +use Symfony\Bridge\Twig\Extension\FormExtension; +use Symfony\Bridge\Twig\Extension\SecurityExtension; +use Symfony\Bridge\Twig\Extension\HttpFoundationExtension; +use Symfony\Bridge\Twig\Extension\HttpKernelExtension; +use Symfony\Bridge\Twig\Extension\WebLinkExtension; +use Symfony\Bridge\Twig\Form\TwigRendererEngine; +use Symfony\Bridge\Twig\Form\TwigRenderer; +use Symfony\Bridge\Twig\Extension\HttpKernelRuntime; +use Symfony\Component\WebLink\HttpHeaderSerializer; + +/** + * Twig integration for Silex. + * + * @author Fabien Potencier + */ +class TwigServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['twig.options'] = array(); + $app['twig.form.templates'] = array('form_div_layout.html.twig'); + $app['twig.path'] = array(); + $app['twig.templates'] = array(); + + $app['twig.date.format'] = 'F j, Y H:i'; + $app['twig.date.interval_format'] = '%d days'; + $app['twig.date.timezone'] = null; + + $app['twig.number_format.decimals'] = 0; + $app['twig.number_format.decimal_point'] = '.'; + $app['twig.number_format.thousands_separator'] = ','; + + $app['twig'] = function ($app) { + $app['twig.options'] = array_replace( + array( + 'charset' => $app['charset'], + 'debug' => $app['debug'], + 'strict_variables' => $app['debug'], + ), $app['twig.options'] + ); + + $twig = $app['twig.environment_factory']($app); + // registered for BC, but should not be used anymore + // deprecated and should probably be removed in Silex 3.0 + $twig->addGlobal('app', $app); + + $coreExtension = $twig->getExtension('Twig_Extension_Core'); + + $coreExtension->setDateFormat($app['twig.date.format'], $app['twig.date.interval_format']); + + if (null !== $app['twig.date.timezone']) { + $coreExtension->setTimezone($app['twig.date.timezone']); + } + + $coreExtension->setNumberFormat($app['twig.number_format.decimals'], $app['twig.number_format.decimal_point'], $app['twig.number_format.thousands_separator']); + + if ($app['debug']) { + $twig->addExtension(new \Twig_Extension_Debug()); + } + + if (class_exists('Symfony\Bridge\Twig\Extension\RoutingExtension')) { + $app['twig.app_variable'] = function ($app) { + $var = new AppVariable(); + if (isset($app['security.token_storage'])) { + $var->setTokenStorage($app['security.token_storage']); + } + if (isset($app['request_stack'])) { + $var->setRequestStack($app['request_stack']); + } + $var->setDebug($app['debug']); + + return $var; + }; + + $twig->addGlobal('global', $app['twig.app_variable']); + + if (isset($app['request_stack'])) { + $twig->addExtension(new HttpFoundationExtension($app['request_stack'])); + $twig->addExtension(new RoutingExtension($app['url_generator'])); + } + + if (isset($app['translator'])) { + $twig->addExtension(new TranslationExtension($app['translator'])); + } + + if (isset($app['security.authorization_checker'])) { + $twig->addExtension(new SecurityExtension($app['security.authorization_checker'])); + } + + if (isset($app['fragment.handler'])) { + $app['fragment.renderer.hinclude']->setTemplating($twig); + + $twig->addExtension(new HttpKernelExtension($app['fragment.handler'])); + } + + if (isset($app['assets.packages'])) { + $twig->addExtension(new AssetExtension($app['assets.packages'])); + } + + if (isset($app['form.factory'])) { + $app['twig.form.engine'] = function ($app) use ($twig) { + return new TwigRendererEngine($app['twig.form.templates'], $twig); + }; + + $app['twig.form.renderer'] = function ($app) { + $csrfTokenManager = isset($app['csrf.token_manager']) ? $app['csrf.token_manager'] : null; + + return new TwigRenderer($app['twig.form.engine'], $csrfTokenManager); + }; + + $twig->addExtension(new FormExtension(class_exists(HttpKernelRuntime::class) ? null : $app['twig.form.renderer'])); + + // add loader for Symfony built-in form templates + $reflected = new \ReflectionClass('Symfony\Bridge\Twig\Extension\FormExtension'); + $path = dirname($reflected->getFileName()).'/../Resources/views/Form'; + $app['twig.loader']->addLoader(new \Twig_Loader_Filesystem($path)); + } + + if (isset($app['var_dumper.cloner'])) { + $twig->addExtension(new DumpExtension($app['var_dumper.cloner'])); + } + + if (class_exists(HttpKernelRuntime::class)) { + $twig->addRuntimeLoader($app['twig.runtime_loader']); + } + + if (class_exists(HttpHeaderSerializer::class) && class_exists(WebLinkExtension::class)) { + $twig->addExtension(new WebLinkExtension($app['request_stack'])); + } + } + + return $twig; + }; + + $app['twig.loader.filesystem'] = function ($app) { + return new \Twig_Loader_Filesystem($app['twig.path']); + }; + + $app['twig.loader.array'] = function ($app) { + return new \Twig_Loader_Array($app['twig.templates']); + }; + + $app['twig.loader'] = function ($app) { + return new \Twig_Loader_Chain(array( + $app['twig.loader.array'], + $app['twig.loader.filesystem'], + )); + }; + + $app['twig.environment_factory'] = $app->protect(function ($app) { + return new \Twig_Environment($app['twig.loader'], $app['twig.options']); + }); + + $app['twig.runtime.httpkernel'] = function ($app) { + return new HttpKernelRuntime($app['fragment.handler']); + }; + + $app['twig.runtimes'] = function ($app) { + return array( + HttpKernelRuntime::class => 'twig.runtime.httpkernel', + TwigRenderer::class => 'twig.form.renderer', + ); + }; + + $app['twig.runtime_loader'] = function ($app) { + return new RuntimeLoader($app, $app['twig.runtimes']); + }; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Validator/ConstraintValidatorFactory.php b/vendor/silex/silex/src/Silex/Provider/Validator/ConstraintValidatorFactory.php new file mode 100644 index 00000000..9f5e499d --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Validator/ConstraintValidatorFactory.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 Silex\Provider\Validator; + +use Pimple\Container; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidatorFactory as BaseConstraintValidatorFactory; + +/** + * Uses a service container to create constraint validators with dependencies. + * + * @author Kris Wallsmith + * @author Alex Kalyvitis + */ +class ConstraintValidatorFactory extends BaseConstraintValidatorFactory +{ + /** + * @var Container + */ + protected $container; + + /** + * @var array + */ + protected $serviceNames; + + /** + * Constructor. + * + * @param Container $container DI container + * @param array $serviceNames Validator service names + */ + public function __construct(Container $container, array $serviceNames = array(), $propertyAccessor = null) + { + parent::__construct($propertyAccessor); + + $this->container = $container; + $this->serviceNames = $serviceNames; + } + + /** + * {@inheritdoc} + */ + public function getInstance(Constraint $constraint) + { + $name = $constraint->validatedBy(); + + if (isset($this->serviceNames[$name])) { + return $this->container[$this->serviceNames[$name]]; + } + + return parent::getInstance($constraint); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/ValidatorServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/ValidatorServiceProvider.php new file mode 100644 index 00000000..d89a3cb5 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/ValidatorServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Provider\Validator\ConstraintValidatorFactory; +use Symfony\Component\Validator\Validator; +use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory; +use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader; +use Symfony\Component\Validator\Validation; + +/** + * Symfony Validator component Provider. + * + * @author Fabien Potencier + */ +class ValidatorServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['validator'] = function ($app) { + return $app['validator.builder']->getValidator(); + }; + + $app['validator.builder'] = function ($app) { + $builder = Validation::createValidatorBuilder(); + $builder->setConstraintValidatorFactory($app['validator.validator_factory']); + $builder->setTranslationDomain('validators'); + $builder->addObjectInitializers($app['validator.object_initializers']); + $builder->setMetadataFactory($app['validator.mapping.class_metadata_factory']); + if (isset($app['translator'])) { + $builder->setTranslator($app['translator']); + } + + return $builder; + }; + + $app['validator.mapping.class_metadata_factory'] = function ($app) { + return new LazyLoadingMetadataFactory(new StaticMethodLoader()); + }; + + $app['validator.validator_factory'] = function () use ($app) { + return new ConstraintValidatorFactory($app, $app['validator.validator_service_ids']); + }; + + $app['validator.object_initializers'] = function ($app) { + return array(); + }; + + $app['validator.validator_service_ids'] = array(); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/VarDumperServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/VarDumperServiceProvider.php new file mode 100644 index 00000000..7c40b5ee --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/VarDumperServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Application; +use Silex\Api\BootableProviderInterface; +use Symfony\Component\VarDumper\VarDumper; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * Symfony Var Dumper component Provider. + * + * @author Fabien Potencier + */ +class VarDumperServiceProvider implements ServiceProviderInterface, BootableProviderInterface +{ + public function register(Container $app) + { + $app['var_dumper.cli_dumper'] = function ($app) { + return new CliDumper($app['var_dumper.dump_destination'], $app['charset']); + }; + + $app['var_dumper.cloner'] = function ($app) { + return new VarCloner(); + }; + + $app['var_dumper.dump_destination'] = null; + } + + public function boot(Application $app) + { + if (!$app['debug']) { + return; + } + + // This code is here to lazy load the dump stack. This default + // configuration for CLI mode is overridden in HTTP mode on + // 'kernel.request' event + VarDumper::setHandler(function ($var) use ($app) { + VarDumper::setHandler($handler = function ($var) use ($app) { + $app['var_dumper.cli_dumper']->dump($app['var_dumper.cloner']->cloneVar($var)); + }); + $handler($var); + }); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/composer.json b/vendor/silex/silex/src/Silex/Provider/composer.json new file mode 100644 index 00000000..99b7a491 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/composer.json @@ -0,0 +1,31 @@ +{ + "minimum-stability": "dev", + "name": "silex/providers", + "description": "The Silex providers", + "keywords": ["microframework"], + "homepage": "http://silex.sensiolabs.org", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0", + "silex/api": "~2.1" + }, + "autoload": { + "psr-4": { "Silex\\Provider\\": "" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/vendor/silex/silex/src/Silex/Route.php b/vendor/silex/silex/src/Silex/Route.php new file mode 100644 index 00000000..99e82d87 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Route.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 Silex; + +use Symfony\Component\Routing\Route as BaseRoute; + +/** + * A wrapper for a controller, mapped to a route. + * + * @author Fabien Potencier + */ +class Route extends BaseRoute +{ + /** + * Constructor. + * + * Available options: + * + * * compiler_class: A class name able to compile this route instance (RouteCompiler by default) + * + * @param string $path The path pattern to match + * @param array $defaults An array of default parameter values + * @param array $requirements An array of requirements for parameters (regexes) + * @param array $options An array of options + * @param string $host The host pattern to match + * @param string|array $schemes A required URI scheme or an array of restricted schemes + * @param string|array $methods A required HTTP method or an array of restricted methods + */ + public function __construct($path = '/', array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array()) + { + // overridden constructor to make $path optional + parent::__construct($path, $defaults, $requirements, $options, $host, $schemes, $methods); + } + + /** + * Sets the route code that should be executed when matched. + * + * @param callable $to PHP callback that returns the response when matched + * + * @return Route $this The current Route instance + */ + public function run($to) + { + $this->setDefault('_controller', $to); + + return $this; + } + + /** + * Sets the requirement for a route variable. + * + * @param string $variable The variable name + * @param string $regexp The regexp to apply + * + * @return Route $this The current route instance + */ + public function assert($variable, $regexp) + { + $this->setRequirement($variable, $regexp); + + return $this; + } + + /** + * Sets the default value for a route variable. + * + * @param string $variable The variable name + * @param mixed $default The default value + * + * @return Route $this The current Route instance + */ + public function value($variable, $default) + { + $this->setDefault($variable, $default); + + return $this; + } + + /** + * Sets a converter for a route variable. + * + * @param string $variable The variable name + * @param mixed $callback A PHP callback that converts the original value + * + * @return Route $this The current Route instance + */ + public function convert($variable, $callback) + { + $converters = $this->getOption('_converters'); + $converters[$variable] = $callback; + $this->setOption('_converters', $converters); + + return $this; + } + + /** + * Sets the requirement for the HTTP method. + * + * @param string $method The HTTP method name. Multiple methods can be supplied, delimited by a pipe character '|', eg. 'GET|POST' + * + * @return Route $this The current Route instance + */ + public function method($method) + { + $this->setMethods(explode('|', $method)); + + return $this; + } + + /** + * Sets the requirement of host on this Route. + * + * @param string $host The host for which this route should be enabled + * + * @return Route $this The current Route instance + */ + public function host($host) + { + $this->setHost($host); + + return $this; + } + + /** + * Sets the requirement of HTTP (no HTTPS) on this Route. + * + * @return Route $this The current Route instance + */ + public function requireHttp() + { + $this->setSchemes('http'); + + return $this; + } + + /** + * Sets the requirement of HTTPS on this Route. + * + * @return Route $this The current Route instance + */ + public function requireHttps() + { + $this->setSchemes('https'); + + return $this; + } + + /** + * Sets a callback to handle before triggering the route callback. + * + * @param mixed $callback A PHP callback to be triggered when the Route is matched, just before the route callback + * + * @return Route $this The current Route instance + */ + public function before($callback) + { + $callbacks = $this->getOption('_before_middlewares'); + $callbacks[] = $callback; + $this->setOption('_before_middlewares', $callbacks); + + return $this; + } + + /** + * Sets a callback to handle after the route callback. + * + * @param mixed $callback A PHP callback to be triggered after the route callback + * + * @return Route $this The current Route instance + */ + public function after($callback) + { + $callbacks = $this->getOption('_after_middlewares'); + $callbacks[] = $callback; + $this->setOption('_after_middlewares', $callbacks); + + return $this; + } + + /** + * Sets a condition for the route to match. + * + * @param string $condition The condition + * + * @return Route $this The current Route instance + */ + public function when($condition) + { + $this->setCondition($condition); + + return $this; + } +} diff --git a/vendor/silex/silex/src/Silex/Route/SecurityTrait.php b/vendor/silex/silex/src/Silex/Route/SecurityTrait.php new file mode 100644 index 00000000..d42ba2fb --- /dev/null +++ b/vendor/silex/silex/src/Silex/Route/SecurityTrait.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 Silex\Route; + +use Symfony\Component\Security\Core\Exception\AccessDeniedException; + +/** + * Security trait. + * + * @author Fabien Potencier + */ +trait SecurityTrait +{ + public function secure($roles) + { + $this->before(function ($request, $app) use ($roles) { + if (!$app['security.authorization_checker']->isGranted($roles)) { + throw new AccessDeniedException(); + } + }); + } +} diff --git a/vendor/silex/silex/src/Silex/ServiceControllerResolver.php b/vendor/silex/silex/src/Silex/ServiceControllerResolver.php new file mode 100644 index 00000000..87f91b04 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ServiceControllerResolver.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 Silex; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; + +/** + * Enables name_of_service:method_name syntax for declaring controllers. + * + * @link http://silex.sensiolabs.org/doc/providers/service_controller.html + */ +class ServiceControllerResolver implements ControllerResolverInterface +{ + protected $controllerResolver; + protected $callbackResolver; + + /** + * Constructor. + * + * @param ControllerResolverInterface $controllerResolver A ControllerResolverInterface instance to delegate to + * @param CallbackResolver $callbackResolver A service resolver instance + */ + public function __construct(ControllerResolverInterface $controllerResolver, CallbackResolver $callbackResolver) + { + $this->controllerResolver = $controllerResolver; + $this->callbackResolver = $callbackResolver; + } + + /** + * {@inheritdoc} + */ + public function getController(Request $request) + { + $controller = $request->attributes->get('_controller', null); + + if (!$this->callbackResolver->isValid($controller)) { + return $this->controllerResolver->getController($request); + } + + return $this->callbackResolver->convertCallback($controller); + } + + /** + * {@inheritdoc} + */ + public function getArguments(Request $request, $controller) + { + return $this->controllerResolver->getArguments($request, $controller); + } +} diff --git a/vendor/silex/silex/src/Silex/ViewListenerWrapper.php b/vendor/silex/silex/src/Silex/ViewListenerWrapper.php new file mode 100644 index 00000000..a67ec938 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ViewListenerWrapper.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; + +/** + * Wraps view listeners. + * + * @author Dave Marshall + */ +class ViewListenerWrapper +{ + private $app; + private $callback; + + /** + * Constructor. + * + * @param Application $app An Application instance + * @param mixed $callback + */ + public function __construct(Application $app, $callback) + { + $this->app = $app; + $this->callback = $callback; + } + + public function __invoke(GetResponseForControllerResultEvent $event) + { + $controllerResult = $event->getControllerResult(); + $callback = $this->app['callback_resolver']->resolveCallback($this->callback); + + if (!$this->shouldRun($callback, $controllerResult)) { + return; + } + + $response = call_user_func($callback, $controllerResult, $event->getRequest()); + + if ($response instanceof Response) { + $event->setResponse($response); + } elseif (null !== $response) { + $event->setControllerResult($response); + } + } + + private function shouldRun($callback, $controllerResult) + { + if (is_array($callback)) { + $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]); + } elseif (is_object($callback) && !$callback instanceof \Closure) { + $callbackReflection = new \ReflectionObject($callback); + $callbackReflection = $callbackReflection->getMethod('__invoke'); + } else { + $callbackReflection = new \ReflectionFunction($callback); + } + + if ($callbackReflection->getNumberOfParameters() > 0) { + $parameters = $callbackReflection->getParameters(); + $expectedControllerResult = $parameters[0]; + + if ($expectedControllerResult->getClass() && (!is_object($controllerResult) || !$expectedControllerResult->getClass()->isInstance($controllerResult))) { + return false; + } + + if ($expectedControllerResult->isArray() && !is_array($controllerResult)) { + return false; + } + + if (method_exists($expectedControllerResult, 'isCallable') && $expectedControllerResult->isCallable() && !is_callable($controllerResult)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/silex/silex/src/Silex/WebTestCase.php b/vendor/silex/silex/src/Silex/WebTestCase.php new file mode 100644 index 00000000..644bb050 --- /dev/null +++ b/vendor/silex/silex/src/Silex/WebTestCase.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 Silex; + +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * WebTestCase is the base class for functional tests. + * + * @author Igor Wiedler + */ +abstract class WebTestCase extends \PHPUnit_Framework_TestCase +{ + /** + * HttpKernelInterface instance. + * + * @var HttpKernelInterface + */ + protected $app; + + /** + * PHPUnit setUp for setting up the application. + * + * Note: Child classes that define a setUp method must call + * parent::setUp(). + */ + protected function setUp() + { + $this->app = $this->createApplication(); + } + + /** + * Creates the application. + * + * @return HttpKernelInterface + */ + abstract public function createApplication(); + + /** + * Creates a Client. + * + * @param array $server Server parameters + * + * @return Client A Client instance + */ + public function createClient(array $server = array()) + { + if (!class_exists('Symfony\Component\BrowserKit\Client')) { + throw new \LogicException('Component "symfony/browser-kit" is required by WebTestCase.'.PHP_EOL.'Run composer require symfony/browser-kit'); + } + + return new Client($this->app, $server); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/FormApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/FormApplication.php new file mode 100644 index 00000000..5851a4c9 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/FormApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class FormApplication extends Application +{ + use Application\FormTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/FormTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/FormTraitTest.php new file mode 100644 index 00000000..74e56c05 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/FormTraitTest.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\FormServiceProvider; + +/** + * FormTrait test cases. + * + * @author Fabien Potencier + */ +class FormTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testForm() + { + $this->assertInstanceOf('Symfony\Component\Form\FormBuilder', $this->createApplication()->form()); + } + + public function createApplication() + { + $app = new FormApplication(); + $app->register(new FormServiceProvider()); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/MonologApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/MonologApplication.php new file mode 100644 index 00000000..9fec12fb --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/MonologApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class MonologApplication extends Application +{ + use Application\MonologTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/MonologTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/MonologTraitTest.php new file mode 100644 index 00000000..a2e3acbf --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/MonologTraitTest.php @@ -0,0 +1,47 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\MonologServiceProvider; +use Monolog\Handler\TestHandler; +use Monolog\Logger; + +/** + * MonologTrait test cases. + * + * @author Fabien Potencier + */ +class MonologTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testLog() + { + $app = $this->createApplication(); + + $app->log('Foo'); + $app->log('Bar', array(), Logger::DEBUG); + $this->assertTrue($app['monolog.handler']->hasInfo('Foo')); + $this->assertTrue($app['monolog.handler']->hasDebug('Bar')); + } + + public function createApplication() + { + $app = new MonologApplication(); + $app->register(new MonologServiceProvider(), array( + 'monolog.handler' => function () use ($app) { + return new TestHandler($app['monolog.level']); + }, + 'monolog.logfile' => 'php://memory', + )); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/SecurityApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/SecurityApplication.php new file mode 100644 index 00000000..dc85999e --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/SecurityApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class SecurityApplication extends Application +{ + use Application\SecurityTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/SecurityTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/SecurityTraitTest.php new file mode 100644 index 00000000..e91eda73 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/SecurityTraitTest.php @@ -0,0 +1,90 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\SecurityServiceProvider; +use Symfony\Component\Security\Core\User\User; +use Symfony\Component\HttpFoundation\Request; + +/** + * SecurityTrait test cases. + * + * @author Fabien Potencier + */ +class SecurityTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testEncodePassword() + { + $app = $this->createApplication(array( + 'fabien' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + )); + + $user = new User('foo', 'bar'); + $password = 'foo'; + $encoded = $app->encodePassword($user, $password); + + $this->assertTrue( + $app['security.encoder_factory']->getEncoder($user)->isPasswordValid($encoded, $password, $user->getSalt()) + ); + } + + /** + * @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException + */ + public function testIsGrantedWithoutTokenThrowsException() + { + $app = $this->createApplication(); + $app->get('/', function () { return 'foo'; }); + $app->handle(Request::create('/')); + $app->isGranted('ROLE_ADMIN'); + } + + public function testIsGranted() + { + $request = Request::create('/'); + + $app = $this->createApplication(array( + 'fabien' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + 'monique' => array('ROLE_USER', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + )); + $app->get('/', function () { return 'foo'; }); + + // User is Monique (ROLE_USER) + $request->headers->set('PHP_AUTH_USER', 'monique'); + $request->headers->set('PHP_AUTH_PW', 'foo'); + $app->handle($request); + $this->assertTrue($app->isGranted('ROLE_USER')); + $this->assertFalse($app->isGranted('ROLE_ADMIN')); + + // User is Fabien (ROLE_ADMIN) + $request->headers->set('PHP_AUTH_USER', 'fabien'); + $request->headers->set('PHP_AUTH_PW', 'foo'); + $app->handle($request); + $this->assertFalse($app->isGranted('ROLE_USER')); + $this->assertTrue($app->isGranted('ROLE_ADMIN')); + } + + public function createApplication($users = array()) + { + $app = new SecurityApplication(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + 'users' => $users, + ), + ), + )); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerApplication.php new file mode 100644 index 00000000..6a28d539 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class SwiftmailerApplication extends Application +{ + use Application\SwiftmailerTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerTraitTest.php new file mode 100644 index 00000000..923db39e --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerTraitTest.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\SwiftmailerServiceProvider; + +/** + * SwiftmailerTrait test cases. + * + * @author Fabien Potencier + */ +class SwiftmailerTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testMail() + { + $app = $this->createApplication(); + + $message = $this->getMockBuilder('Swift_Message')->disableOriginalConstructor()->getMock(); + $app['mailer'] = $mailer = $this->getMockBuilder('Swift_Mailer')->disableOriginalConstructor()->getMock(); + $mailer->expects($this->once()) + ->method('send') + ->with($message) + ; + + $app->mail($message); + } + + public function createApplication() + { + $app = new SwiftmailerApplication(); + $app->register(new SwiftmailerServiceProvider()); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/TranslationApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/TranslationApplication.php new file mode 100644 index 00000000..3e51b9c2 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/TranslationApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class TranslationApplication extends Application +{ + use Application\TranslationTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/TranslationTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/TranslationTraitTest.php new file mode 100644 index 00000000..f2837c14 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/TranslationTraitTest.php @@ -0,0 +1,46 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\TranslationServiceProvider; + +/** + * TranslationTrait test cases. + * + * @author Fabien Potencier + */ +class TranslationTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testTrans() + { + $app = $this->createApplication(); + $app['translator'] = $translator = $this->getMockBuilder('Symfony\Component\Translation\Translator')->disableOriginalConstructor()->getMock(); + $translator->expects($this->once())->method('trans'); + $app->trans('foo'); + } + + public function testTransChoice() + { + $app = $this->createApplication(); + $app['translator'] = $translator = $this->getMockBuilder('Symfony\Component\Translation\Translator')->disableOriginalConstructor()->getMock(); + $translator->expects($this->once())->method('transChoice'); + $app->transChoice('foo', 2); + } + + public function createApplication() + { + $app = new TranslationApplication(); + $app->register(new TranslationServiceProvider()); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/TwigApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/TwigApplication.php new file mode 100644 index 00000000..f1bb4738 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/TwigApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class TwigApplication extends Application +{ + use Application\TwigTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/TwigTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/TwigTraitTest.php new file mode 100644 index 00000000..9435f7c8 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/TwigTraitTest.php @@ -0,0 +1,80 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\TwigServiceProvider; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * TwigTrait test cases. + * + * @author Fabien Potencier + */ +class TwigTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testRender() + { + $app = $this->createApplication(); + + $app['twig'] = $mailer = $this->getMockBuilder('Twig_Environment')->disableOriginalConstructor()->getMock(); + $mailer->expects($this->once())->method('render')->will($this->returnValue('foo')); + + $response = $app->render('view'); + $this->assertEquals('Symfony\Component\HttpFoundation\Response', get_class($response)); + $this->assertEquals('foo', $response->getContent()); + } + + public function testRenderKeepResponse() + { + $app = $this->createApplication(); + + $app['twig'] = $mailer = $this->getMockBuilder('Twig_Environment')->disableOriginalConstructor()->getMock(); + $mailer->expects($this->once())->method('render')->will($this->returnValue('foo')); + + $response = $app->render('view', array(), new Response('', 404)); + $this->assertEquals(404, $response->getStatusCode()); + } + + public function testRenderForStream() + { + $app = $this->createApplication(); + + $app['twig'] = $mailer = $this->getMockBuilder('Twig_Environment')->disableOriginalConstructor()->getMock(); + $mailer->expects($this->once())->method('display')->will($this->returnCallback(function () { echo 'foo'; })); + + $response = $app->render('view', array(), new StreamedResponse()); + $this->assertEquals('Symfony\Component\HttpFoundation\StreamedResponse', get_class($response)); + + ob_start(); + $response->send(); + $this->assertEquals('foo', ob_get_clean()); + } + + public function testRenderView() + { + $app = $this->createApplication(); + + $app['twig'] = $mailer = $this->getMockBuilder('Twig_Environment')->disableOriginalConstructor()->getMock(); + $mailer->expects($this->once())->method('render'); + + $app->renderView('view'); + } + + public function createApplication() + { + $app = new TwigApplication(); + $app->register(new TwigServiceProvider()); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorApplication.php new file mode 100644 index 00000000..4239af46 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class UrlGeneratorApplication extends Application +{ + use Application\UrlGeneratorTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorTraitTest.php new file mode 100644 index 00000000..822c6eb3 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorTraitTest.php @@ -0,0 +1,38 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * UrlGeneratorTrait test cases. + * + * @author Fabien Potencier + */ +class UrlGeneratorTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testUrl() + { + $app = new UrlGeneratorApplication(); + $app['url_generator'] = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->disableOriginalConstructor()->getMock(); + $app['url_generator']->expects($this->once())->method('generate')->with('foo', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $app->url('foo'); + } + + public function testPath() + { + $app = new UrlGeneratorApplication(); + $app['url_generator'] = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->disableOriginalConstructor()->getMock(); + $app['url_generator']->expects($this->once())->method('generate')->with('foo', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + $app->path('foo'); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ApplicationTest.php b/vendor/silex/silex/tests/Silex/Tests/ApplicationTest.php new file mode 100644 index 00000000..9a23cd1d --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ApplicationTest.php @@ -0,0 +1,719 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Fig\Link\GenericLinkProvider; +use Fig\Link\Link; +use Silex\Application; +use Silex\ControllerCollection; +use Silex\Api\ControllerProviderInterface; +use Silex\Route; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\Routing\RouteCollection; + +/** + * Application test cases. + * + * @author Igor Wiedler + */ +class ApplicationTest extends \PHPUnit_Framework_TestCase +{ + public function testMatchReturnValue() + { + $app = new Application(); + + $returnValue = $app->match('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + + $returnValue = $app->get('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + + $returnValue = $app->post('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + + $returnValue = $app->put('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + + $returnValue = $app->patch('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + + $returnValue = $app->delete('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + } + + public function testConstructorInjection() + { + // inject a custom parameter + $params = array('param' => 'value'); + $app = new Application($params); + $this->assertSame($params['param'], $app['param']); + + // inject an existing parameter + $params = array('locale' => 'value'); + $app = new Application($params); + $this->assertSame($params['locale'], $app['locale']); + } + + public function testGetRequest() + { + $request = Request::create('/'); + + $app = new Application(); + $app->get('/', function (Request $req) use ($request) { + return $request === $req ? 'ok' : 'ko'; + }); + + $this->assertEquals('ok', $app->handle($request)->getContent()); + } + + public function testGetRoutesWithNoRoutes() + { + $app = new Application(); + + $routes = $app['routes']; + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $routes); + $this->assertEquals(0, count($routes->all())); + } + + public function testGetRoutesWithRoutes() + { + $app = new Application(); + + $app->get('/foo', function () { + return 'foo'; + }); + + $app->get('/bar')->run(function () { + return 'bar'; + }); + + $routes = $app['routes']; + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $routes); + $this->assertEquals(0, count($routes->all())); + $app->flush(); + $this->assertEquals(2, count($routes->all())); + } + + public function testOnCoreController() + { + $app = new Application(); + + $app->get('/foo/{foo}', function (\ArrayObject $foo) { + return $foo['foo']; + })->convert('foo', function ($foo) { return new \ArrayObject(array('foo' => $foo)); }); + + $response = $app->handle(Request::create('/foo/bar')); + $this->assertEquals('bar', $response->getContent()); + + $app->get('/foo/{foo}/{bar}', function (\ArrayObject $foo) { + return $foo['foo']; + })->convert('foo', function ($foo, Request $request) { return new \ArrayObject(array('foo' => $foo.$request->attributes->get('bar'))); }); + + $response = $app->handle(Request::create('/foo/foo/bar')); + $this->assertEquals('foobar', $response->getContent()); + } + + public function testOn() + { + $app = new Application(); + $app['pass'] = false; + + $app->on('test', function (Event $e) use ($app) { + $app['pass'] = true; + }); + + $app['dispatcher']->dispatch('test'); + + $this->assertTrue($app['pass']); + } + + public function testAbort() + { + $app = new Application(); + + try { + $app->abort(404); + $this->fail(); + } catch (HttpException $e) { + $this->assertEquals(404, $e->getStatusCode()); + } + } + + /** + * @dataProvider escapeProvider + */ + public function testEscape($expected, $text) + { + $app = new Application(); + + $this->assertEquals($expected, $app->escape($text)); + } + + public function escapeProvider() + { + return array( + array('<', '<'), + array('>', '>'), + array('"', '"'), + array("'", "'"), + array('abc', 'abc'), + ); + } + + public function testControllersAsMethods() + { + $app = new Application(); + unset($app['exception_handler']); + + $app->get('/{name}', 'Silex\Tests\FooController::barAction'); + + $this->assertEquals('Hello Fabien', $app->handle(Request::create('/Fabien'))->getContent()); + } + + public function testApplicationTypeHintWorks() + { + $app = new SpecialApplication(); + unset($app['exception_handler']); + + $app->get('/{name}', 'Silex\Tests\FooController::barSpecialAction'); + + $this->assertEquals('Hello Fabien in Silex\Tests\SpecialApplication', $app->handle(Request::create('/Fabien'))->getContent()); + } + + /** + * @requires PHP 7.0 + */ + public function testPhp7TypeHintWorks() + { + $app = new SpecialApplication(); + unset($app['exception_handler']); + + $app->get('/{name}', 'Silex\Tests\Fixtures\Php7Controller::typehintedAction'); + + $this->assertEquals('Hello Fabien in Silex\Tests\SpecialApplication', $app->handle(Request::create('/Fabien'))->getContent()); + } + + public function testHttpSpec() + { + $app = new Application(); + $app['charset'] = 'ISO-8859-1'; + + $app->get('/', function () { + return 'hello'; + }); + + // content is empty for HEAD requests + $response = $app->handle(Request::create('/', 'HEAD')); + $this->assertEquals('', $response->getContent()); + + // charset is appended to Content-Type + $response = $app->handle(Request::create('/')); + + $this->assertEquals('text/html; charset=ISO-8859-1', $response->headers->get('Content-Type')); + } + + public function testRoutesMiddlewares() + { + $app = new Application(); + + $test = $this; + + $middlewareTarget = array(); + $beforeMiddleware1 = function (Request $request) use (&$middlewareTarget, $test) { + $test->assertEquals('/reached', $request->getRequestUri()); + $middlewareTarget[] = 'before_middleware1_triggered'; + }; + $beforeMiddleware2 = function (Request $request) use (&$middlewareTarget, $test) { + $test->assertEquals('/reached', $request->getRequestUri()); + $middlewareTarget[] = 'before_middleware2_triggered'; + }; + $beforeMiddleware3 = function (Request $request) use (&$middlewareTarget, $test) { + throw new \Exception('This middleware shouldn\'t run!'); + }; + + $afterMiddleware1 = function (Request $request, Response $response) use (&$middlewareTarget, $test) { + $test->assertEquals('/reached', $request->getRequestUri()); + $middlewareTarget[] = 'after_middleware1_triggered'; + }; + $afterMiddleware2 = function (Request $request, Response $response) use (&$middlewareTarget, $test) { + $test->assertEquals('/reached', $request->getRequestUri()); + $middlewareTarget[] = 'after_middleware2_triggered'; + }; + $afterMiddleware3 = function (Request $request, Response $response) use (&$middlewareTarget, $test) { + throw new \Exception('This middleware shouldn\'t run!'); + }; + + $app->get('/reached', function () use (&$middlewareTarget) { + $middlewareTarget[] = 'route_triggered'; + + return 'hello'; + }) + ->before($beforeMiddleware1) + ->before($beforeMiddleware2) + ->after($afterMiddleware1) + ->after($afterMiddleware2); + + $app->get('/never-reached', function () use (&$middlewareTarget) { + throw new \Exception('This route shouldn\'t run!'); + }) + ->before($beforeMiddleware3) + ->after($afterMiddleware3); + + $result = $app->handle(Request::create('/reached')); + + $this->assertSame(array('before_middleware1_triggered', 'before_middleware2_triggered', 'route_triggered', 'after_middleware1_triggered', 'after_middleware2_triggered'), $middlewareTarget); + $this->assertEquals('hello', $result->getContent()); + } + + public function testRoutesBeforeMiddlewaresWithResponseObject() + { + $app = new Application(); + + $app->get('/foo', function () { + throw new \Exception('This route shouldn\'t run!'); + }) + ->before(function () { + return new Response('foo'); + }); + + $request = Request::create('/foo'); + $result = $app->handle($request); + + $this->assertEquals('foo', $result->getContent()); + } + + public function testRoutesAfterMiddlewaresWithResponseObject() + { + $app = new Application(); + + $app->get('/foo', function () { + return new Response('foo'); + }) + ->after(function () { + return new Response('bar'); + }); + + $request = Request::create('/foo'); + $result = $app->handle($request); + + $this->assertEquals('bar', $result->getContent()); + } + + public function testRoutesBeforeMiddlewaresWithRedirectResponseObject() + { + $app = new Application(); + + $app->get('/foo', function () { + throw new \Exception('This route shouldn\'t run!'); + }) + ->before(function () use ($app) { + return $app->redirect('/bar'); + }); + + $request = Request::create('/foo'); + $result = $app->handle($request); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $result); + $this->assertEquals('/bar', $result->getTargetUrl()); + } + + public function testRoutesBeforeMiddlewaresTriggeredAfterSilexBeforeFilters() + { + $app = new Application(); + + $middlewareTarget = array(); + $middleware = function (Request $request) use (&$middlewareTarget) { + $middlewareTarget[] = 'middleware_triggered'; + }; + + $app->get('/foo', function () use (&$middlewareTarget) { + $middlewareTarget[] = 'route_triggered'; + }) + ->before($middleware); + + $app->before(function () use (&$middlewareTarget) { + $middlewareTarget[] = 'before_triggered'; + }); + + $app->handle(Request::create('/foo')); + + $this->assertSame(array('before_triggered', 'middleware_triggered', 'route_triggered'), $middlewareTarget); + } + + public function testRoutesAfterMiddlewaresTriggeredBeforeSilexAfterFilters() + { + $app = new Application(); + + $middlewareTarget = array(); + $middleware = function (Request $request) use (&$middlewareTarget) { + $middlewareTarget[] = 'middleware_triggered'; + }; + + $app->get('/foo', function () use (&$middlewareTarget) { + $middlewareTarget[] = 'route_triggered'; + }) + ->after($middleware); + + $app->after(function () use (&$middlewareTarget) { + $middlewareTarget[] = 'after_triggered'; + }); + + $app->handle(Request::create('/foo')); + + $this->assertSame(array('route_triggered', 'middleware_triggered', 'after_triggered'), $middlewareTarget); + } + + public function testFinishFilter() + { + $containerTarget = array(); + + $app = new Application(); + + $app->finish(function () use (&$containerTarget) { + $containerTarget[] = '4_filterFinish'; + }); + + $app->get('/foo', function () use (&$containerTarget) { + $containerTarget[] = '1_routeTriggered'; + + return new StreamedResponse(function () use (&$containerTarget) { + $containerTarget[] = '3_responseSent'; + }); + }); + + $app->after(function () use (&$containerTarget) { + $containerTarget[] = '2_filterAfter'; + }); + + $app->run(Request::create('/foo')); + + $this->assertSame(array('1_routeTriggered', '2_filterAfter', '3_responseSent', '4_filterFinish'), $containerTarget); + } + + /** + * @expectedException \RuntimeException + */ + public function testNonResponseAndNonNullReturnFromRouteBeforeMiddlewareShouldThrowRuntimeException() + { + $app = new Application(); + + $middleware = function (Request $request) { + return 'string return'; + }; + + $app->get('/', function () { + return 'hello'; + }) + ->before($middleware); + + $app->handle(Request::create('/'), HttpKernelInterface::MASTER_REQUEST, false); + } + + /** + * @expectedException \RuntimeException + */ + public function testNonResponseAndNonNullReturnFromRouteAfterMiddlewareShouldThrowRuntimeException() + { + $app = new Application(); + + $middleware = function (Request $request) { + return 'string return'; + }; + + $app->get('/', function () { + return 'hello'; + }) + ->after($middleware); + + $app->handle(Request::create('/'), HttpKernelInterface::MASTER_REQUEST, false); + } + + public function testSubRequest() + { + $app = new Application(); + $app->get('/sub', function (Request $request) { + return new Response('foo'); + }); + $app->get('/', function (Request $request) use ($app) { + return $app->handle(Request::create('/sub'), HttpKernelInterface::SUB_REQUEST); + }); + + $this->assertEquals('foo', $app->handle(Request::create('/'))->getContent()); + } + + public function testRegisterShouldReturnSelf() + { + $app = new Application(); + $provider = $this->getMockBuilder('Pimple\ServiceProviderInterface')->getMock(); + + $this->assertSame($app, $app->register($provider)); + } + + public function testMountShouldReturnSelf() + { + $app = new Application(); + $mounted = new ControllerCollection(new Route()); + $mounted->get('/{name}', function ($name) { return new Response($name); }); + + $this->assertSame($app, $app->mount('/hello', $mounted)); + } + + public function testMountPreservesOrder() + { + $app = new Application(); + $mounted = new ControllerCollection(new Route()); + $mounted->get('/mounted')->bind('second'); + + $app->get('/before')->bind('first'); + $app->mount('/', $mounted); + $app->get('/after')->bind('third'); + $app->flush(); + + $this->assertEquals(array('first', 'second', 'third'), array_keys(iterator_to_array($app['routes']))); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The "mount" method takes either a "ControllerCollection" instance, "ControllerProviderInterface" instance, or a callable. + */ + public function testMountNullException() + { + $app = new Application(); + $app->mount('/exception', null); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The method "Silex\Tests\IncorrectControllerCollection::connect" must return a "ControllerCollection" instance. Got: "NULL" + */ + public function testMountWrongConnectReturnValueException() + { + $app = new Application(); + $app->mount('/exception', new IncorrectControllerCollection()); + } + + public function testMountCallable() + { + $app = new Application(); + $app->mount('/prefix', function (ControllerCollection $coll) { + $coll->get('/path'); + }); + + $app->flush(); + + $this->assertEquals(1, $app['routes']->count()); + } + + public function testSendFile() + { + $app = new Application(); + + $response = $app->sendFile(__FILE__, 200, array('Content-Type: application/php')); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\BinaryFileResponse', $response); + $this->assertEquals(__FILE__, (string) $response->getFile()); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The "homepage" route must have code to run when it matches. + */ + public function testGetRouteCollectionWithRouteWithoutController() + { + $app = new Application(); + unset($app['exception_handler']); + $app->match('/')->bind('homepage'); + $app->handle(Request::create('/')); + } + + public function testBeforeFilterOnMountedControllerGroupIsolatedToGroup() + { + $app = new Application(); + $app->match('/', function () { return new Response('ok'); }); + $mounted = $app['controllers_factory']; + $mounted->before(function () { return new Response('not ok'); }); + $app->mount('/group', $mounted); + + $response = $app->handle(Request::create('/')); + $this->assertEquals('ok', $response->getContent()); + } + + public function testViewListenerWithPrimitive() + { + $app = new Application(); + $app->get('/foo', function () { return 123; }); + $app->view(function ($view, Request $request) { + return new Response($view); + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('123', $response->getContent()); + } + + public function testViewListenerWithArrayTypeHint() + { + $app = new Application(); + $app->get('/foo', function () { return array('ok'); }); + $app->view(function (array $view) { + return new Response($view[0]); + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('ok', $response->getContent()); + } + + public function testViewListenerWithObjectTypeHint() + { + $app = new Application(); + $app->get('/foo', function () { return (object) array('name' => 'world'); }); + $app->view(function (\stdClass $view) { + return new Response('Hello '.$view->name); + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('Hello world', $response->getContent()); + } + + public function testViewListenerWithCallableTypeHint() + { + $app = new Application(); + $app->get('/foo', function () { return function () { return 'world'; }; }); + $app->view(function (callable $view) { + return new Response('Hello '.$view()); + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('Hello world', $response->getContent()); + } + + public function testViewListenersCanBeChained() + { + $app = new Application(); + $app->get('/foo', function () { return (object) array('name' => 'world'); }); + + $app->view(function (\stdClass $view) { + return array('msg' => 'Hello '.$view->name); + }); + + $app->view(function (array $view) { + return $view['msg']; + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('Hello world', $response->getContent()); + } + + public function testViewListenersAreIgnoredIfNotSuitable() + { + $app = new Application(); + $app->get('/foo', function () { return 'Hello world'; }); + + $app->view(function (\stdClass $view) { + throw new \Exception('View listener was called'); + }); + + $app->view(function (array $view) { + throw new \Exception('View listener was called'); + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('Hello world', $response->getContent()); + } + + public function testViewListenersResponsesAreNotUsedIfNull() + { + $app = new Application(); + $app->get('/foo', function () { return 'Hello world'; }); + + $app->view(function ($view) { + return 'Hello view listener'; + }); + + $app->view(function ($view) { + return; + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('Hello view listener', $response->getContent()); + } + + public function testWebLinkListener() + { + $app = new Application(); + + $app->get('/', function () { + return 'hello'; + }); + + $request = Request::create('/'); + $request->attributes->set('_links', (new GenericLinkProvider())->withLink(new Link('preload', '/foo.css'))); + + $response = $app->handle($request); + + $this->assertEquals('; rel="preload"', $response->headers->get('Link')); + } + + public function testDefaultRoutesFactory() + { + $app = new Application(); + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $app['routes']); + } + + public function testOverriddenRoutesFactory() + { + $app = new Application(); + $app['routes_factory'] = $app->factory(function () { + return new RouteCollectionSubClass(); + }); + $this->assertInstanceOf('Silex\Tests\RouteCollectionSubClass', $app['routes']); + } +} + +class FooController +{ + public function barAction(Application $app, $name) + { + return 'Hello '.$app->escape($name); + } + + public function barSpecialAction(SpecialApplication $app, $name) + { + return 'Hello '.$app->escape($name).' in '.get_class($app); + } +} + +class IncorrectControllerCollection implements ControllerProviderInterface +{ + public function connect(Application $app) + { + return; + } +} + +class RouteCollectionSubClass extends RouteCollection +{ +} + +class SpecialApplication extends Application +{ +} diff --git a/vendor/silex/silex/tests/Silex/Tests/CallbackResolverTest.php b/vendor/silex/silex/tests/Silex/Tests/CallbackResolverTest.php new file mode 100644 index 00000000..80503b97 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/CallbackResolverTest.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 Silex\Tests; + +use Pimple\Container; +use Silex\CallbackResolver; + +class CallbackResolverTest extends \PHPUnit_Framework_Testcase +{ + private $app; + private $resolver; + + public function setup() + { + $this->app = new Container(); + $this->resolver = new CallbackResolver($this->app); + } + + public function testShouldResolveCallback() + { + $callable = function () {}; + $this->app['some_service'] = function () { return new \ArrayObject(); }; + $this->app['callable_service'] = function () use ($callable) { + return $callable; + }; + + $this->assertTrue($this->resolver->isValid('some_service:methodName')); + $this->assertTrue($this->resolver->isValid('callable_service')); + $this->assertEquals( + array($this->app['some_service'], 'append'), + $this->resolver->convertCallback('some_service:append') + ); + $this->assertSame($callable, $this->resolver->convertCallback('callable_service')); + } + + /** + * @dataProvider nonStringsAreNotValidProvider + */ + public function testNonStringsAreNotValid($name) + { + $this->assertFalse($this->resolver->isValid($name)); + } + + public function nonStringsAreNotValidProvider() + { + return array( + array(null), + array('some_service::methodName'), + array('missing_service'), + ); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessageRegExp /Service "[a-z_]+" is not callable./ + * @dataProvider shouldThrowAnExceptionIfServiceIsNotCallableProvider + */ + public function testShouldThrowAnExceptionIfServiceIsNotCallable($name) + { + $this->app['non_callable_obj'] = function () { return new \stdClass(); }; + $this->app['non_callable'] = function () { return array(); }; + $this->resolver->convertCallback($name); + } + + public function shouldThrowAnExceptionIfServiceIsNotCallableProvider() + { + return array( + array('non_callable_obj:methodA'), + array('non_callable'), + ); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/CallbackServicesTest.php b/vendor/silex/silex/tests/Silex/Tests/CallbackServicesTest.php new file mode 100644 index 00000000..fe96317e --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/CallbackServicesTest.php @@ -0,0 +1,109 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; +use Silex\Provider\ServiceControllerServiceProvider; + +/** + * Callback as services test cases. + * + * @author Fabien Potencier + */ +class CallbackServicesTest extends \PHPUnit_Framework_TestCase +{ + public $called = array(); + + public function testCallbacksAsServices() + { + $app = new Application(); + $app->register(new ServiceControllerServiceProvider()); + + $app['service'] = function () { + return new CallbackServicesTest(); + }; + + $app->before('service:beforeApp'); + $app->after('service:afterApp'); + $app->finish('service:finishApp'); + $app->error('service:error'); + $app->on('kernel.request', 'service:onRequest'); + + $app + ->match('/', 'service:controller') + ->convert('foo', 'service:convert') + ->before('service:before') + ->after('service:after') + ; + + $request = Request::create('/'); + $response = $app->handle($request); + $app->terminate($request, $response); + + $this->assertEquals(array( + 'BEFORE APP', + 'ON REQUEST', + 'BEFORE', + 'CONVERT', + 'ERROR', + 'AFTER', + 'AFTER APP', + 'FINISH APP', + ), $app['service']->called); + } + + public function controller(Application $app) + { + $app->abort(404); + } + + public function before() + { + $this->called[] = 'BEFORE'; + } + + public function after() + { + $this->called[] = 'AFTER'; + } + + public function beforeApp() + { + $this->called[] = 'BEFORE APP'; + } + + public function afterApp() + { + $this->called[] = 'AFTER APP'; + } + + public function finishApp() + { + $this->called[] = 'FINISH APP'; + } + + public function error() + { + $this->called[] = 'ERROR'; + } + + public function convert() + { + $this->called[] = 'CONVERT'; + } + + public function onRequest() + { + $this->called[] = 'ON REQUEST'; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ControllerCollectionTest.php b/vendor/silex/silex/tests/Silex/Tests/ControllerCollectionTest.php new file mode 100644 index 00000000..d5c9889c --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ControllerCollectionTest.php @@ -0,0 +1,327 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Silex\Controller; +use Silex\ControllerCollection; +use Silex\Exception\ControllerFrozenException; +use Silex\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * ControllerCollection test cases. + * + * @author Igor Wiedler + */ +class ControllerCollectionTest extends \PHPUnit_Framework_TestCase +{ + public function testGetRouteCollectionWithNoRoutes() + { + $controllers = new ControllerCollection(new Route()); + $routes = $controllers->flush(); + $this->assertEquals(0, count($routes->all())); + } + + public function testGetRouteCollectionWithRoutes() + { + $controllers = new ControllerCollection(new Route()); + $controllers->match('/foo', function () {}); + $controllers->match('/bar', function () {}); + + $routes = $controllers->flush(); + $this->assertEquals(2, count($routes->all())); + } + + public function testControllerFreezing() + { + $controllers = new ControllerCollection(new Route()); + + $fooController = $controllers->match('/foo', function () {})->bind('foo'); + $barController = $controllers->match('/bar', function () {})->bind('bar'); + + $controllers->flush(); + + try { + $fooController->bind('foo2'); + $this->fail(); + } catch (ControllerFrozenException $e) { + } + + try { + $barController->bind('bar2'); + $this->fail(); + } catch (ControllerFrozenException $e) { + } + } + + public function testConflictingRouteNames() + { + $controllers = new ControllerCollection(new Route()); + + $mountedRootController = $controllers->match('/', function () {}); + + $mainRootController = new Controller(new Route('/')); + $mainRootController->bind($mainRootController->generateRouteName('main_1')); + + $controllers->flush(); + + $this->assertNotEquals($mainRootController->getRouteName(), $mountedRootController->getRouteName()); + } + + public function testUniqueGeneratedRouteNames() + { + $controllers = new ControllerCollection(new Route()); + + $controllers->match('/a-a', function () {}); + $controllers->match('/a_a', function () {}); + $controllers->match('/a/a', function () {}); + + $routes = $controllers->flush(); + + $this->assertCount(3, $routes->all()); + $this->assertEquals(array('_a_a', '_a_a_1', '_a_a_2'), array_keys($routes->all())); + } + + public function testUniqueGeneratedRouteNamesAmongMounts() + { + $controllers = new ControllerCollection(new Route()); + + $controllers->mount('/root-a', $rootA = new ControllerCollection(new Route())); + $controllers->mount('/root_a', $rootB = new ControllerCollection(new Route())); + + $rootA->match('/leaf', function () {}); + $rootB->match('/leaf', function () {}); + + $routes = $controllers->flush(); + + $this->assertCount(2, $routes->all()); + $this->assertEquals(array('_root_a_leaf', '_root_a_leaf_1'), array_keys($routes->all())); + } + + public function testUniqueGeneratedRouteNamesAmongNestedMounts() + { + $controllers = new ControllerCollection(new Route()); + + $controllers->mount('/root-a', $rootA = new ControllerCollection(new Route())); + $controllers->mount('/root_a', $rootB = new ControllerCollection(new Route())); + + $rootA->mount('/tree', $treeA = new ControllerCollection(new Route())); + $rootB->mount('/tree', $treeB = new ControllerCollection(new Route())); + + $treeA->match('/leaf', function () {}); + $treeB->match('/leaf', function () {}); + + $routes = $controllers->flush(); + + $this->assertCount(2, $routes->all()); + $this->assertEquals(array('_root_a_tree_leaf', '_root_a_tree_leaf_1'), array_keys($routes->all())); + } + + public function testMountCallable() + { + $controllers = new ControllerCollection(new Route()); + $controllers->mount('/prefix', function (ControllerCollection $coll) { + $coll->mount('/path', function ($coll) { + $coll->get('/part'); + }); + }); + + $routes = $controllers->flush(); + $this->assertEquals('/prefix/path/part', current($routes->all())->getPath()); + } + + public function testMountCallableProperClone() + { + $controllers = new ControllerCollection(new Route(), new RouteCollection()); + $controllers->get('/'); + + $subControllers = null; + $controllers->mount('/prefix', function (ControllerCollection $coll) use (&$subControllers) { + $subControllers = $coll; + $coll->get('/'); + }); + + $routes = $controllers->flush(); + $subRoutes = $subControllers->flush(); + $this->assertTrue($routes->count() == 2 && $subRoutes->count() == 0); + } + + public function testMountControllersFactory() + { + $testControllers = new ControllerCollection(new Route()); + $controllers = new ControllerCollection(new Route(), null, function () use ($testControllers) { + return $testControllers; + }); + + $controllers->mount('/prefix', function ($mounted) use ($testControllers) { + $this->assertSame($mounted, $testControllers); + }); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The "mount" method takes either a "ControllerCollection" instance or callable. + */ + public function testMountCallableException() + { + $controllers = new ControllerCollection(new Route()); + $controllers->mount('/prefix', ''); + } + + public function testAssert() + { + $controllers = new ControllerCollection(new Route()); + $controllers->assert('id', '\d+'); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->assert('name', '\w+')->assert('extra', '.*'); + $controllers->assert('extra', '\w+'); + + $this->assertEquals('\d+', $controller->getRoute()->getRequirement('id')); + $this->assertEquals('\w+', $controller->getRoute()->getRequirement('name')); + $this->assertEquals('\w+', $controller->getRoute()->getRequirement('extra')); + } + + public function testValue() + { + $controllers = new ControllerCollection(new Route()); + $controllers->value('id', '1'); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->value('name', 'Fabien')->value('extra', 'Symfony'); + $controllers->value('extra', 'Twig'); + + $this->assertEquals('1', $controller->getRoute()->getDefault('id')); + $this->assertEquals('Fabien', $controller->getRoute()->getDefault('name')); + $this->assertEquals('Twig', $controller->getRoute()->getDefault('extra')); + } + + public function testConvert() + { + $controllers = new ControllerCollection(new Route()); + $controllers->convert('id', '1'); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->convert('name', 'Fabien')->convert('extra', 'Symfony'); + $controllers->convert('extra', 'Twig'); + + $this->assertEquals(array('id' => '1', 'name' => 'Fabien', 'extra' => 'Twig'), $controller->getRoute()->getOption('_converters')); + } + + public function testRequireHttp() + { + $controllers = new ControllerCollection(new Route()); + $controllers->requireHttp(); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->requireHttps(); + + $this->assertEquals(array('https'), $controller->getRoute()->getSchemes()); + + $controllers->requireHttp(); + + $this->assertEquals(array('http'), $controller->getRoute()->getSchemes()); + } + + public function testBefore() + { + $controllers = new ControllerCollection(new Route()); + $controllers->before('mid1'); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->before('mid2'); + $controllers->before('mid3'); + + $this->assertEquals(array('mid1', 'mid2', 'mid3'), $controller->getRoute()->getOption('_before_middlewares')); + } + + public function testAfter() + { + $controllers = new ControllerCollection(new Route()); + $controllers->after('mid1'); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->after('mid2'); + $controllers->after('mid3'); + + $this->assertEquals(array('mid1', 'mid2', 'mid3'), $controller->getRoute()->getOption('_after_middlewares')); + } + + public function testWhen() + { + $controllers = new ControllerCollection(new Route()); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->when('request.isSecure() == true'); + + $this->assertEquals('request.isSecure() == true', $controller->getRoute()->getCondition()); + } + + public function testRouteExtension() + { + $route = new MyRoute1(); + + $controller = new ControllerCollection($route); + $controller->foo('foo'); + + $this->assertEquals('foo', $route->foo); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRouteMethodDoesNotExist() + { + $route = new MyRoute1(); + + $controller = new ControllerCollection($route); + $controller->bar(); + } + + public function testNestedCollectionRouteCallbacks() + { + $cl1 = new ControllerCollection(new MyRoute1()); + $cl2 = new ControllerCollection(new MyRoute1()); + + $c1 = $cl2->match('/c1', function () {}); + $cl1->mount('/foo', $cl2); + $c2 = $cl2->match('/c2', function () {}); + $cl1->before('before'); + $c3 = $cl2->match('/c3', function () {}); + + $cl1->flush(); + + $this->assertEquals(array('before'), $c1->getRoute()->getOption('_before_middlewares')); + $this->assertEquals(array('before'), $c2->getRoute()->getOption('_before_middlewares')); + $this->assertEquals(array('before'), $c3->getRoute()->getOption('_before_middlewares')); + } + + public function testRoutesFactoryOmitted() + { + $controllers = new ControllerCollection(new Route()); + $routes = $controllers->flush(); + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $routes); + } + + public function testRoutesFactoryInConstructor() + { + $app = new Application(); + $app['routes_factory'] = $app->factory(function () { + return new RouteCollectionSubClass2(); + }); + + $controllers = new ControllerCollection(new Route(), $app['routes_factory']); + $routes = $controllers->flush(); + $this->assertInstanceOf('Silex\Tests\RouteCollectionSubClass2', $routes); + } +} + +class MyRoute1 extends Route +{ + public $foo; + + public function foo($value) + { + $this->foo = $value; + } +} + +class RouteCollectionSubClass2 extends RouteCollection +{ +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ControllerResolverTest.php b/vendor/silex/silex/tests/Silex/Tests/ControllerResolverTest.php new file mode 100644 index 00000000..e4483886 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ControllerResolverTest.php @@ -0,0 +1,38 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\ControllerResolver; +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; + +/** + * ControllerResolver test cases. + * + * @author Fabien Potencier + */ +class ControllerResolverTest extends \PHPUnit_Framework_TestCase +{ + /** + * @group legacy + */ + public function testGetArguments() + { + $app = new Application(); + $resolver = new ControllerResolver($app); + + $controller = function (Application $app) {}; + + $args = $resolver->getArguments(Request::create('/'), $controller); + $this->assertSame($app, $args[0]); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ControllerTest.php b/vendor/silex/silex/tests/Silex/Tests/ControllerTest.php new file mode 100644 index 00000000..791563f4 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ControllerTest.php @@ -0,0 +1,132 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Controller; +use Silex\Route; + +/** + * Controller test cases. + * + * @author Igor Wiedler + */ +class ControllerTest extends \PHPUnit_Framework_TestCase +{ + public function testBind() + { + $controller = new Controller(new Route('/foo')); + $ret = $controller->bind('foo'); + + $this->assertSame($ret, $controller); + $this->assertEquals('foo', $controller->getRouteName()); + } + + /** + * @expectedException \Silex\Exception\ControllerFrozenException + */ + public function testBindOnFrozenControllerShouldThrowException() + { + $controller = new Controller(new Route('/foo')); + $controller->bind('foo'); + $controller->freeze(); + $controller->bind('bar'); + } + + public function testAssert() + { + $controller = new Controller(new Route('/foo/{bar}')); + $ret = $controller->assert('bar', '\d+'); + + $this->assertSame($ret, $controller); + $this->assertEquals(array('bar' => '\d+'), $controller->getRoute()->getRequirements()); + } + + public function testValue() + { + $controller = new Controller(new Route('/foo/{bar}')); + $ret = $controller->value('bar', 'foo'); + + $this->assertSame($ret, $controller); + $this->assertEquals(array('bar' => 'foo'), $controller->getRoute()->getDefaults()); + } + + public function testConvert() + { + $controller = new Controller(new Route('/foo/{bar}')); + $ret = $controller->convert('bar', $func = function ($bar) { return $bar; }); + + $this->assertSame($ret, $controller); + $this->assertEquals(array('bar' => $func), $controller->getRoute()->getOption('_converters')); + } + + public function testRun() + { + $controller = new Controller(new Route('/foo/{bar}')); + $ret = $controller->run($cb = function () { return 'foo'; }); + + $this->assertSame($ret, $controller); + $this->assertEquals($cb, $controller->getRoute()->getDefault('_controller')); + } + + /** + * @dataProvider provideRouteAndExpectedRouteName + */ + public function testDefaultRouteNameGeneration(Route $route, $prefix, $expectedRouteName) + { + $controller = new Controller($route); + $controller->bind($controller->generateRouteName($prefix)); + + $this->assertEquals($expectedRouteName, $controller->getRouteName()); + } + + public function provideRouteAndExpectedRouteName() + { + return array( + array(new Route('/Invalid%Symbols#Stripped', array(), array(), array(), '', array(), array('POST')), '', 'POST_InvalidSymbolsStripped'), + array(new Route('/post/{id}', array(), array(), array(), '', array(), array('GET')), '', 'GET_post_id'), + array(new Route('/colon:pipe|dashes-escaped'), '', '_colon_pipe_dashes_escaped'), + array(new Route('/underscores_and.periods'), '', '_underscores_and.periods'), + array(new Route('/post/{id}', array(), array(), array(), '', array(), array('GET')), 'prefix', 'GET_prefix_post_id'), + ); + } + + public function testRouteExtension() + { + $route = new MyRoute(); + + $controller = new Controller($route); + $controller->foo('foo'); + + $this->assertEquals('foo', $route->foo); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRouteMethodDoesNotExist() + { + $route = new MyRoute(); + + $controller = new Controller($route); + $controller->bar(); + } +} + +class MyRoute extends Route +{ + public $foo; + + public function foo($value) + { + $this->foo = $value; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/EventListener/LogListenerTest.php b/vendor/silex/silex/tests/Silex/Tests/EventListener/LogListenerTest.php new file mode 100644 index 00000000..1937ab32 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/EventListener/LogListenerTest.php @@ -0,0 +1,93 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\EventListener; + +use Psr\Log\LogLevel; +use Silex\EventListener\LogListener; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Exception\HttpException; + +/** + * LogListener. + * + * @author Jérôme Tamarelle + */ +class LogListenerTest extends \PHPUnit_Framework_TestCase +{ + public function testRequestListener() + { + $logger = $this->getMockBuilder('Psr\\Log\\LoggerInterface')->getMock(); + $logger + ->expects($this->once()) + ->method('log') + ->with(LogLevel::DEBUG, '> GET /foo') + ; + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new LogListener($logger)); + + $kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\HttpKernelInterface')->getMock(); + + $dispatcher->dispatch(KernelEvents::REQUEST, new GetResponseEvent($kernel, Request::create('/subrequest'), HttpKernelInterface::SUB_REQUEST), 'Skip sub requests'); + + $dispatcher->dispatch(KernelEvents::REQUEST, new GetResponseEvent($kernel, Request::create('/foo'), HttpKernelInterface::MASTER_REQUEST), 'Log master requests'); + } + + public function testResponseListener() + { + $logger = $this->getMockBuilder('Psr\\Log\\LoggerInterface')->getMock(); + $logger + ->expects($this->once()) + ->method('log') + ->with(LogLevel::DEBUG, '< 301') + ; + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new LogListener($logger)); + + $kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\HttpKernelInterface')->getMock(); + + $dispatcher->dispatch(KernelEvents::RESPONSE, new FilterResponseEvent($kernel, Request::create('/foo'), HttpKernelInterface::SUB_REQUEST, Response::create('subrequest', 200)), 'Skip sub requests'); + + $dispatcher->dispatch(KernelEvents::RESPONSE, new FilterResponseEvent($kernel, Request::create('/foo'), HttpKernelInterface::MASTER_REQUEST, Response::create('bar', 301)), 'Log master requests'); + } + + public function testExceptionListener() + { + $logger = $this->getMockBuilder('Psr\\Log\\LoggerInterface')->getMock(); + $logger + ->expects($this->at(0)) + ->method('log') + ->with(LogLevel::CRITICAL, 'RuntimeException: Fatal error (uncaught exception) at '.__FILE__.' line '.(__LINE__ + 13)) + ; + $logger + ->expects($this->at(1)) + ->method('log') + ->with(LogLevel::ERROR, 'Symfony\Component\HttpKernel\Exception\HttpException: Http error (uncaught exception) at '.__FILE__.' line '.(__LINE__ + 9)) + ; + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new LogListener($logger)); + + $kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\HttpKernelInterface')->getMock(); + + $dispatcher->dispatch(KernelEvents::EXCEPTION, new GetResponseForExceptionEvent($kernel, Request::create('/foo'), HttpKernelInterface::SUB_REQUEST, new \RuntimeException('Fatal error'))); + $dispatcher->dispatch(KernelEvents::EXCEPTION, new GetResponseForExceptionEvent($kernel, Request::create('/foo'), HttpKernelInterface::SUB_REQUEST, new HttpException(400, 'Http error'))); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ExceptionHandlerTest.php b/vendor/silex/silex/tests/Silex/Tests/ExceptionHandlerTest.php new file mode 100644 index 00000000..24e9a0dd --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ExceptionHandlerTest.php @@ -0,0 +1,406 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Error handler test cases. + * + * @author Igor Wiedler + */ +class ExceptionHandlerTest extends \PHPUnit_Framework_TestCase +{ + public function testExceptionHandlerExceptionNoDebug() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('

Whoops, looks like something went wrong.

', $response->getContent()); + $this->assertEquals(500, $response->getStatusCode()); + } + + public function testExceptionHandlerExceptionDebug() + { + $app = new Application(); + $app['debug'] = true; + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + + $this->assertContains('foo exception', $response->getContent()); + $this->assertEquals(500, $response->getStatusCode()); + } + + public function testExceptionHandlerNotFoundNoDebug() + { + $app = new Application(); + $app['debug'] = false; + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('

Sorry, the page you are looking for could not be found.

', $response->getContent()); + $this->assertEquals(404, $response->getStatusCode()); + } + + public function testExceptionHandlerNotFoundDebug() + { + $app = new Application(); + $app['debug'] = true; + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('No route found for "GET /foo"', html_entity_decode($response->getContent())); + $this->assertEquals(404, $response->getStatusCode()); + } + + public function testExceptionHandlerMethodNotAllowedNoDebug() + { + $app = new Application(); + $app['debug'] = false; + + $app->get('/foo', function () { return 'foo'; }); + + $request = Request::create('/foo', 'POST'); + $response = $app->handle($request); + $this->assertContains('

Whoops, looks like something went wrong.

', $response->getContent()); + $this->assertEquals(405, $response->getStatusCode()); + $this->assertEquals('GET', $response->headers->get('Allow')); + } + + public function testExceptionHandlerMethodNotAllowedDebug() + { + $app = new Application(); + $app['debug'] = true; + + $app->get('/foo', function () { return 'foo'; }); + + $request = Request::create('/foo', 'POST'); + $response = $app->handle($request); + $this->assertContains('No route found for "POST /foo": Method Not Allowed (Allow: GET)', html_entity_decode($response->getContent())); + $this->assertEquals(405, $response->getStatusCode()); + $this->assertEquals('GET', $response->headers->get('Allow')); + } + + public function testNoExceptionHandler() + { + $app = new Application(); + unset($app['exception_handler']); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + try { + $request = Request::create('/foo'); + $app->handle($request); + $this->fail('->handle() should not catch exceptions where no error handler was supplied'); + } catch (\RuntimeException $e) { + $this->assertEquals('foo exception', $e->getMessage()); + } + } + + public function testOneExceptionHandler() + { + $app = new Application(); + + $app->match('/500', function () { + throw new \RuntimeException('foo exception'); + }); + + $app->match('/404', function () { + throw new NotFoundHttpException('foo exception'); + }); + + $app->get('/405', function () { return 'foo'; }); + + $app->error(function ($e, $code) { + return new Response('foo exception handler'); + }); + + $response = $this->checkRouteResponse($app, '/500', 'foo exception handler'); + $this->assertEquals(500, $response->getStatusCode()); + + $response = $app->handle(Request::create('/404')); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $app->handle(Request::create('/405', 'POST')); + $this->assertEquals(405, $response->getStatusCode()); + $this->assertEquals('GET', $response->headers->get('Allow')); + } + + public function testMultipleExceptionHandlers() + { + $app = new Application(); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $errors = 0; + + $app->error(function ($e) use (&$errors) { + ++$errors; + }); + + $app->error(function ($e) use (&$errors) { + ++$errors; + }); + + $app->error(function ($e) use (&$errors) { + ++$errors; + + return new Response('foo exception handler'); + }); + + $app->error(function ($e) use (&$errors) { + // should not execute + ++$errors; + }); + + $request = Request::create('/foo'); + $this->checkRouteResponse($app, '/foo', 'foo exception handler', 'should return the first response returned by an exception handler'); + + $this->assertEquals(3, $errors, 'should execute error handlers until a response is returned'); + } + + public function testNoResponseExceptionHandler() + { + $app = new Application(); + unset($app['exception_handler']); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $errors = 0; + + $app->error(function ($e) use (&$errors) { + ++$errors; + }); + + try { + $request = Request::create('/foo'); + $app->handle($request); + $this->fail('->handle() should not catch exceptions where an empty error handler was supplied'); + } catch (\RuntimeException $e) { + $this->assertEquals('foo exception', $e->getMessage()); + } catch (\LogicException $e) { + $this->assertEquals('foo exception', $e->getPrevious()->getMessage()); + } + + $this->assertEquals(1, $errors, 'should execute the error handler'); + } + + public function testStringResponseExceptionHandler() + { + $app = new Application(); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $app->error(function ($e) { + return 'foo exception handler'; + }); + + $request = Request::create('/foo'); + $this->checkRouteResponse($app, '/foo', 'foo exception handler', 'should accept a string response from the error handler'); + } + + public function testExceptionHandlerException() + { + $app = new Application(); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $app->error(function ($e) { + throw new \RuntimeException('foo exception handler exception'); + }); + + try { + $request = Request::create('/foo'); + $app->handle($request); + $this->fail('->handle() should not catch exceptions thrown from an error handler'); + } catch (\RuntimeException $e) { + $this->assertEquals('foo exception handler exception', $e->getMessage()); + } + } + + public function testRemoveExceptionHandlerAfterDispatcherAccess() + { + $app = new Application(); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $app->before(function () { + // just making sure the dispatcher gets created + }); + + unset($app['exception_handler']); + + try { + $request = Request::create('/foo'); + $app->handle($request); + $this->fail('default exception handler should have been removed'); + } catch (\RuntimeException $e) { + $this->assertEquals('foo exception', $e->getMessage()); + } + } + + public function testExceptionHandlerWithDefaultException() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + throw new \Exception(); + }); + + $app->error(function (\Exception $e) { + return new Response('Exception thrown', 500); + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Exception thrown', $response->getContent()); + $this->assertEquals(500, $response->getStatusCode()); + } + + public function testExceptionHandlerWithStandardException() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + // Throw a normal exception + throw new \Exception(); + }); + + // Register 2 error handlers, each with a specified Exception class + // Since we throw a standard Exception above only + // the second error handler should fire + $app->error(function (\LogicException $e) { // Extends \Exception + + return 'Caught LogicException'; + }); + $app->error(function (\Exception $e) { + return 'Caught Exception'; + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Caught Exception', $response->getContent()); + } + + public function testExceptionHandlerWithSpecifiedException() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + // Throw a specified exception + throw new \LogicException(); + }); + + // Register 2 error handlers, each with a specified Exception class + // Since we throw a LogicException above + // the first error handler should fire + $app->error(function (\LogicException $e) { // Extends \Exception + + return 'Caught LogicException'; + }); + $app->error(function (\Exception $e) { + return 'Caught Exception'; + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Caught LogicException', $response->getContent()); + } + + public function testExceptionHandlerWithSpecifiedExceptionInReverseOrder() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + // Throw a specified exception + throw new \LogicException(); + }); + + // Register the \Exception error handler first, since the + // error handler works with an instanceof mechanism the + // second more specific error handler should not fire since + // the \Exception error handler is registered first and also + // captures all exceptions that extend it + $app->error(function (\Exception $e) { + return 'Caught Exception'; + }); + $app->error(function (\LogicException $e) { // Extends \Exception + + return 'Caught LogicException'; + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Caught Exception', $response->getContent()); + } + + public function testExceptionHandlerWithArrayStyleCallback() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + throw new \Exception(); + }); + + // Array style callback for error handler + $app->error(array($this, 'exceptionHandler')); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Caught Exception', $response->getContent()); + } + + protected function checkRouteResponse($app, $path, $expectedContent, $method = 'get', $message = null) + { + $request = Request::create($path, $method); + $response = $app->handle($request); + $this->assertEquals($expectedContent, $response->getContent(), $message); + + return $response; + } + + public function exceptionHandler() + { + return 'Caught Exception'; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Fixtures/Php7Controller.php b/vendor/silex/silex/tests/Silex/Tests/Fixtures/Php7Controller.php new file mode 100644 index 00000000..c16f14ed --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Fixtures/Php7Controller.php @@ -0,0 +1,22 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Fixtures; + +use Silex\Application; + +class Php7Controller +{ + public function typehintedAction(Application $application, string $name) + { + return 'Hello '.$application->escape($name).' in '.get_class($application); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/FunctionalTest.php b/vendor/silex/silex/tests/Silex/Tests/FunctionalTest.php new file mode 100644 index 00000000..f2af4ac3 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/FunctionalTest.php @@ -0,0 +1,58 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Silex\Route; +use Silex\ControllerCollection; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Functional test cases. + * + * @author Igor Wiedler + */ +class FunctionalTest extends \PHPUnit_Framework_TestCase +{ + public function testBind() + { + $app = new Application(); + + $app->get('/', function () { + return 'hello'; + }) + ->bind('homepage'); + + $app->get('/foo', function () { + return 'foo'; + }) + ->bind('foo_abc'); + + $app->flush(); + $routes = $app['routes']; + $this->assertInstanceOf('Symfony\Component\Routing\Route', $routes->get('homepage')); + $this->assertInstanceOf('Symfony\Component\Routing\Route', $routes->get('foo_abc')); + } + + public function testMount() + { + $mounted = new ControllerCollection(new Route()); + $mounted->get('/{name}', function ($name) { return new Response($name); }); + + $app = new Application(); + $app->mount('/hello', $mounted); + + $response = $app->handle(Request::create('/hello/Silex')); + $this->assertEquals('Silex', $response->getContent()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/JsonTest.php b/vendor/silex/silex/tests/Silex/Tests/JsonTest.php new file mode 100644 index 00000000..5eb13362 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/JsonTest.php @@ -0,0 +1,56 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; + +/** + * JSON test cases. + * + * @author Igor Wiedler + */ +class JsonTest extends \PHPUnit_Framework_TestCase +{ + public function testJsonReturnsJsonResponse() + { + $app = new Application(); + + $response = $app->json(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $response = json_decode($response->getContent(), true); + $this->assertSame(array(), $response); + } + + public function testJsonUsesData() + { + $app = new Application(); + + $response = $app->json(array('foo' => 'bar')); + $this->assertSame('{"foo":"bar"}', $response->getContent()); + } + + public function testJsonUsesStatus() + { + $app = new Application(); + + $response = $app->json(array(), 202); + $this->assertSame(202, $response->getStatusCode()); + } + + public function testJsonUsesHeaders() + { + $app = new Application(); + + $response = $app->json(array(), 200, array('ETag' => 'foo')); + $this->assertSame('foo', $response->headers->get('ETag')); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/LazyDispatcherTest.php b/vendor/silex/silex/tests/Silex/Tests/LazyDispatcherTest.php new file mode 100644 index 00000000..1b4c580c --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/LazyDispatcherTest.php @@ -0,0 +1,59 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; + +class LazyDispatcherTest extends \PHPUnit_Framework_TestCase +{ + /** @test */ + public function beforeMiddlewareShouldNotCreateDispatcherEarly() + { + $dispatcherCreated = false; + + $app = new Application(); + $app->extend('dispatcher', function ($dispatcher, $app) use (&$dispatcherCreated) { + $dispatcherCreated = true; + + return $dispatcher; + }); + + $app->before(function () {}); + + $this->assertFalse($dispatcherCreated); + + $request = Request::create('/'); + $app->handle($request); + + $this->assertTrue($dispatcherCreated); + } + + /** @test */ + public function eventHelpersShouldDirectlyAddListenersAfterBoot() + { + $app = new Application(); + + $fired = false; + $app->get('/', function () use ($app, &$fired) { + $app->finish(function () use (&$fired) { + $fired = true; + }); + }); + + $request = Request::create('/'); + $response = $app->handle($request); + $app->terminate($request, $response); + + $this->assertTrue($fired, 'Event was not fired'); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/LazyRequestMatcherTest.php b/vendor/silex/silex/tests/Silex/Tests/LazyRequestMatcherTest.php new file mode 100644 index 00000000..7ecb5b2f --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/LazyRequestMatcherTest.php @@ -0,0 +1,77 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Symfony\Component\HttpFoundation\Request; +use Silex\Provider\Routing\LazyRequestMatcher; + +/** + * LazyRequestMatcher test case. + * + * @author Leszek Prabucki + */ +class LazyRequestMatcherTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Silex\LazyRequestMatcher::getRequestMatcher + */ + public function testUserMatcherIsCreatedLazily() + { + $callCounter = 0; + $requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + + $matcher = new LazyRequestMatcher(function () use ($requestMatcher, &$callCounter) { + ++$callCounter; + + return $requestMatcher; + }); + + $this->assertEquals(0, $callCounter); + $request = Request::create('path'); + $matcher->matchRequest($request); + $this->assertEquals(1, $callCounter); + } + + /** + * @expectedException LogicException + * @expectedExceptionMessage Factory supplied to LazyRequestMatcher must return implementation of Symfony\Component\Routing\RequestMatcherInterface. + */ + public function testThatCanInjectRequestMatcherOnly() + { + $matcher = new LazyRequestMatcher(function () { + return 'someMatcher'; + }); + + $request = Request::create('path'); + $matcher->matchRequest($request); + } + + /** + * @covers Silex\LazyRequestMatcher::matchRequest + */ + public function testMatchIsProxy() + { + $request = Request::create('path'); + $matcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $matcher->expects($this->once()) + ->method('matchRequest') + ->with($request) + ->will($this->returnValue('matcherReturnValue')); + + $matcher = new LazyRequestMatcher(function () use ($matcher) { + return $matcher; + }); + $result = $matcher->matchRequest($request); + + $this->assertEquals('matcherReturnValue', $result); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/LocaleTest.php b/vendor/silex/silex/tests/Silex/Tests/LocaleTest.php new file mode 100644 index 00000000..ada57be4 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/LocaleTest.php @@ -0,0 +1,83 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Silex\Provider\LocaleServiceProvider; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Locale test cases. + * + * @author Fabien Potencier + */ +class LocaleTest extends \PHPUnit_Framework_TestCase +{ + public function testLocale() + { + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app->get('/', function (Request $request) { return $request->getLocale(); }); + $response = $app->handle(Request::create('/')); + $this->assertEquals('en', $response->getContent()); + + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app['locale'] = 'fr'; + $app->get('/', function (Request $request) { return $request->getLocale(); }); + $response = $app->handle(Request::create('/')); + $this->assertEquals('fr', $response->getContent()); + + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app->get('/{_locale}', function (Request $request) { return $request->getLocale(); }); + $response = $app->handle(Request::create('/es')); + $this->assertEquals('es', $response->getContent()); + } + + public function testLocaleInSubRequests() + { + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app->get('/embed/{_locale}', function (Request $request) { return $request->getLocale(); }); + $app->get('/{_locale}', function (Request $request) use ($app) { + return $request->getLocale().$app->handle(Request::create('/embed/es'), HttpKernelInterface::SUB_REQUEST)->getContent().$request->getLocale(); + }); + $response = $app->handle(Request::create('/fr')); + $this->assertEquals('fresfr', $response->getContent()); + + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app->get('/embed', function (Request $request) { return $request->getLocale(); }); + $app->get('/{_locale}', function (Request $request) use ($app) { + return $request->getLocale().$app->handle(Request::create('/embed'), HttpKernelInterface::SUB_REQUEST)->getContent().$request->getLocale(); + }); + $response = $app->handle(Request::create('/fr')); + // locale in sub-request must be "en" as this is the value if the sub-request is converted to an ESI + $this->assertEquals('frenfr', $response->getContent()); + } + + public function testLocaleWithBefore() + { + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app->before(function (Request $request) use ($app) { $request->setLocale('fr'); }); + $app->get('/embed', function (Request $request) { return $request->getLocale(); }); + $app->get('/', function (Request $request) use ($app) { + return $request->getLocale().$app->handle(Request::create('/embed'), HttpKernelInterface::SUB_REQUEST)->getContent().$request->getLocale(); + }); + $response = $app->handle(Request::create('/')); + // locale in sub-request is "en" as the before filter is only executed for the main request + $this->assertEquals('frenfr', $response->getContent()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/MiddlewareTest.php b/vendor/silex/silex/tests/Silex/Tests/MiddlewareTest.php new file mode 100644 index 00000000..376a42c6 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/MiddlewareTest.php @@ -0,0 +1,307 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Middleware test cases. + * + * @author Igor Wiedler + */ +class MiddlewareTest extends \PHPUnit_Framework_TestCase +{ + public function testBeforeAndAfterFilter() + { + $i = 0; + $test = $this; + + $app = new Application(); + + $app->before(function () use (&$i, $test) { + $test->assertEquals(0, $i); + ++$i; + }); + + $app->match('/foo', function () use (&$i, $test) { + $test->assertEquals(1, $i); + ++$i; + }); + + $app->after(function () use (&$i, $test) { + $test->assertEquals(2, $i); + ++$i; + }); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertEquals(3, $i); + } + + public function testAfterFilterWithResponseObject() + { + $i = 0; + + $app = new Application(); + + $app->match('/foo', function () use (&$i) { + ++$i; + + return new Response('foo'); + }); + + $app->after(function () use (&$i) { + ++$i; + }); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertEquals(2, $i); + } + + public function testMultipleFilters() + { + $i = 0; + $test = $this; + + $app = new Application(); + + $app->before(function () use (&$i, $test) { + $test->assertEquals(0, $i); + ++$i; + }); + + $app->before(function () use (&$i, $test) { + $test->assertEquals(1, $i); + ++$i; + }); + + $app->match('/foo', function () use (&$i, $test) { + $test->assertEquals(2, $i); + ++$i; + }); + + $app->after(function () use (&$i, $test) { + $test->assertEquals(3, $i); + ++$i; + }); + + $app->after(function () use (&$i, $test) { + $test->assertEquals(4, $i); + ++$i; + }); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertEquals(5, $i); + } + + public function testFiltersShouldFireOnException() + { + $i = 0; + + $app = new Application(); + + $app->before(function () use (&$i) { + ++$i; + }); + + $app->match('/foo', function () { + throw new \RuntimeException(); + }); + + $app->after(function () use (&$i) { + ++$i; + }); + + $app->error(function () { + return 'error handled'; + }); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertEquals(2, $i); + } + + public function testFiltersShouldFireOnHttpException() + { + $i = 0; + + $app = new Application(); + + $app->before(function () use (&$i) { + ++$i; + }, Application::EARLY_EVENT); + + $app->after(function () use (&$i) { + ++$i; + }); + + $app->error(function () { + return 'error handled'; + }); + + $request = Request::create('/nowhere'); + $app->handle($request); + + $this->assertEquals(2, $i); + } + + public function testBeforeFilterPreventsBeforeMiddlewaresToBeExecuted() + { + $app = new Application(); + + $app->before(function () { return new Response('app before'); }); + + $app->get('/', function () { + return new Response('test'); + })->before(function () { + return new Response('middleware before'); + }); + + $this->assertEquals('app before', $app->handle(Request::create('/'))->getContent()); + } + + public function testBeforeFilterExceptionsWhenHandlingAnException() + { + $app = new Application(); + + $app->before(function () { throw new \RuntimeException(''); }); + + // even if the before filter throws an exception, we must have the 404 + $this->assertEquals(404, $app->handle(Request::create('/'))->getStatusCode()); + } + + public function testRequestShouldBePopulatedOnBefore() + { + $app = new Application(); + + $app->before(function (Request $request) use ($app) { + $app['project'] = $request->get('project'); + }); + + $app->match('/foo/{project}', function () use ($app) { + return $app['project']; + }); + + $request = Request::create('/foo/bar'); + $this->assertEquals('bar', $app->handle($request)->getContent()); + + $request = Request::create('/foo/baz'); + $this->assertEquals('baz', $app->handle($request)->getContent()); + } + + public function testBeforeFilterAccessesRequestAndCanReturnResponse() + { + $app = new Application(); + + $app->before(function (Request $request) { + return new Response($request->get('name')); + }); + + $app->match('/', function () use ($app) { throw new \Exception('Should never be executed'); }); + + $request = Request::create('/?name=Fabien'); + $this->assertEquals('Fabien', $app->handle($request)->getContent()); + } + + public function testAfterFilterAccessRequestResponse() + { + $app = new Application(); + + $app->after(function (Request $request, Response $response) { + $response->setContent($response->getContent().'---'); + }); + + $app->match('/', function () { return new Response('foo'); }); + + $request = Request::create('/'); + $this->assertEquals('foo---', $app->handle($request)->getContent()); + } + + public function testAfterFilterCanReturnResponse() + { + $app = new Application(); + + $app->after(function (Request $request, Response $response) { + return new Response('bar'); + }); + + $app->match('/', function () { return new Response('foo'); }); + + $request = Request::create('/'); + $this->assertEquals('bar', $app->handle($request)->getContent()); + } + + public function testRouteAndApplicationMiddlewareParameterInjection() + { + $app = new Application(); + + $test = $this; + + $middlewareTarget = array(); + $applicationBeforeMiddleware = function ($request, $app) use (&$middlewareTarget, $test) { + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $request); + $test->assertInstanceOf('\Silex\Application', $app); + $middlewareTarget[] = 'application_before_middleware_triggered'; + }; + + $applicationAfterMiddleware = function ($request, $response, $app) use (&$middlewareTarget, $test) { + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $request); + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $response); + $test->assertInstanceOf('\Silex\Application', $app); + $middlewareTarget[] = 'application_after_middleware_triggered'; + }; + + $applicationFinishMiddleware = function ($request, $response, $app) use (&$middlewareTarget, $test) { + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $request); + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $response); + $test->assertInstanceOf('\Silex\Application', $app); + $middlewareTarget[] = 'application_finish_middleware_triggered'; + }; + + $routeBeforeMiddleware = function ($request, $app) use (&$middlewareTarget, $test) { + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $request); + $test->assertInstanceOf('\Silex\Application', $app); + $middlewareTarget[] = 'route_before_middleware_triggered'; + }; + + $routeAfterMiddleware = function ($request, $response, $app) use (&$middlewareTarget, $test) { + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $request); + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $response); + $test->assertInstanceOf('\Silex\Application', $app); + $middlewareTarget[] = 'route_after_middleware_triggered'; + }; + + $app->before($applicationBeforeMiddleware); + $app->after($applicationAfterMiddleware); + $app->finish($applicationFinishMiddleware); + + $app->match('/', function () { + return new Response('foo'); + }) + ->before($routeBeforeMiddleware) + ->after($routeAfterMiddleware); + + $request = Request::create('/'); + $response = $app->handle($request); + $app->terminate($request, $response); + + $this->assertSame(array('application_before_middleware_triggered', 'route_before_middleware_triggered', 'route_after_middleware_triggered', 'application_after_middleware_triggered', 'application_finish_middleware_triggered'), $middlewareTarget); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/AssetServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/AssetServiceProviderTest.php new file mode 100644 index 00000000..3dfb5d3f --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/AssetServiceProviderTest.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\AssetServiceProvider; + +class AssetServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testGenerateAssetUrl() + { + $app = new Application(); + $app->register(new AssetServiceProvider(), array( + 'assets.version' => 'v1', + 'assets.version_format' => '%s?version=%s', + 'assets.named_packages' => array( + 'css' => array('version' => 'css2', 'base_path' => '/whatever-makes-sense'), + 'images' => array('base_urls' => array('https://img.example.com')), + ), + )); + + $this->assertEquals('/foo.png?version=v1', $app['assets.packages']->getUrl('/foo.png')); + $this->assertEquals('/whatever-makes-sense/foo.css?css2', $app['assets.packages']->getUrl('foo.css', 'css')); + $this->assertEquals('https://img.example.com/foo.png', $app['assets.packages']->getUrl('/foo.png', 'images')); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/DoctrineServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/DoctrineServiceProviderTest.php new file mode 100644 index 00000000..5a7e9a2d --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/DoctrineServiceProviderTest.php @@ -0,0 +1,116 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Pimple\Container; +use Silex\Application; +use Silex\Provider\DoctrineServiceProvider; + +/** + * DoctrineProvider test cases. + * + * Fabien Potencier + */ +class DoctrineServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testOptionsInitializer() + { + $app = new Application(); + $app->register(new DoctrineServiceProvider()); + + $this->assertEquals($app['db.default_options'], $app['db']->getParams()); + } + + public function testSingleConnection() + { + if (!in_array('sqlite', \PDO::getAvailableDrivers())) { + $this->markTestSkipped('pdo_sqlite is not available'); + } + + $app = new Application(); + $app->register(new DoctrineServiceProvider(), array( + 'db.options' => array('driver' => 'pdo_sqlite', 'memory' => true), + )); + + $db = $app['db']; + $params = $db->getParams(); + $this->assertTrue(array_key_exists('memory', $params)); + $this->assertTrue($params['memory']); + $this->assertInstanceof('Doctrine\DBAL\Driver\PDOSqlite\Driver', $db->getDriver()); + $this->assertEquals(22, $app['db']->fetchColumn('SELECT 22')); + + $this->assertSame($app['dbs']['default'], $db); + } + + public function testMultipleConnections() + { + if (!in_array('sqlite', \PDO::getAvailableDrivers())) { + $this->markTestSkipped('pdo_sqlite is not available'); + } + + $app = new Application(); + $app->register(new DoctrineServiceProvider(), array( + 'dbs.options' => array( + 'sqlite1' => array('driver' => 'pdo_sqlite', 'memory' => true), + 'sqlite2' => array('driver' => 'pdo_sqlite', 'path' => sys_get_temp_dir().'/silex'), + ), + )); + + $db = $app['db']; + $params = $db->getParams(); + $this->assertTrue(array_key_exists('memory', $params)); + $this->assertTrue($params['memory']); + $this->assertInstanceof('Doctrine\DBAL\Driver\PDOSqlite\Driver', $db->getDriver()); + $this->assertEquals(22, $app['db']->fetchColumn('SELECT 22')); + + $this->assertSame($app['dbs']['sqlite1'], $db); + + $db2 = $app['dbs']['sqlite2']; + $params = $db2->getParams(); + $this->assertTrue(array_key_exists('path', $params)); + $this->assertEquals(sys_get_temp_dir().'/silex', $params['path']); + } + + public function testLoggerLoading() + { + if (!in_array('sqlite', \PDO::getAvailableDrivers())) { + $this->markTestSkipped('pdo_sqlite is not available'); + } + + $app = new Application(); + $this->assertTrue(isset($app['logger'])); + $this->assertNull($app['logger']); + $app->register(new DoctrineServiceProvider(), array( + 'dbs.options' => array( + 'sqlite1' => array('driver' => 'pdo_sqlite', 'memory' => true), + ), + )); + $this->assertEquals(22, $app['db']->fetchColumn('SELECT 22')); + $this->assertNull($app['db']->getConfiguration()->getSQLLogger()); + } + + public function testLoggerNotLoaded() + { + if (!in_array('sqlite', \PDO::getAvailableDrivers())) { + $this->markTestSkipped('pdo_sqlite is not available'); + } + + $app = new Container(); + $app->register(new DoctrineServiceProvider(), array( + 'dbs.options' => array( + 'sqlite1' => array('driver' => 'pdo_sqlite', 'memory' => true), + ), + )); + $this->assertEquals(22, $app['db']->fetchColumn('SELECT 22')); + $this->assertNull($app['db']->getConfiguration()->getSQLLogger()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest.php new file mode 100644 index 00000000..0d32a8bf --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest.php @@ -0,0 +1,362 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\FormServiceProvider; +use Silex\Provider\CsrfServiceProvider; +use Silex\Provider\SessionServiceProvider; +use Silex\Provider\TranslationServiceProvider; +use Silex\Provider\ValidatorServiceProvider; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\FormTypeGuesserChain; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\OptionsResolver\OptionsResolverInterface; +use Symfony\Component\Translation\Exception\NotFoundResourceException; + +class FormServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testFormFactoryServiceIsFormFactory() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $this->assertInstanceOf('Symfony\Component\Form\FormFactory', $app['form.factory']); + } + + public function testFormRegistryServiceIsFormRegistry() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $this->assertInstanceOf('Symfony\Component\Form\FormRegistry', $app['form.registry']); + } + + public function testFormServiceProviderWillLoadTypes() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.types', function ($extensions) { + $extensions[] = new DummyFormType(); + + return $extensions; + }); + + $form = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('dummy', 'Silex\Tests\Provider\DummyFormType') + ->getForm(); + + $this->assertInstanceOf('Symfony\Component\Form\Form', $form); + } + + public function testFormServiceProviderWillLoadTypesServices() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app['dummy'] = function () { + return new DummyFormType(); + }; + $app->extend('form.types', function ($extensions) { + $extensions[] = 'dummy'; + + return $extensions; + }); + + $form = $app['form.factory'] + ->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('dummy', 'dummy') + ->getForm(); + + $this->assertInstanceOf('Symfony\Component\Form\Form', $form); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid form type. The silex service "dummy" does not exist. + */ + public function testNonExistentTypeService() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.types', function ($extensions) { + $extensions[] = 'dummy'; + + return $extensions; + }); + + $app['form.factory'] + ->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('dummy', 'dummy') + ->getForm(); + } + + public function testFormServiceProviderWillLoadTypeExtensions() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.type.extensions', function ($extensions) { + $extensions[] = new DummyFormTypeExtension(); + + return $extensions; + }); + + $form = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType', array('image_path' => 'webPath')) + ->getForm(); + + $this->assertInstanceOf('Symfony\Component\Form\Form', $form); + } + + public function testFormServiceProviderWillLoadTypeExtensionsServices() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app['dummy.form.type.extension'] = function () { + return new DummyFormTypeExtension(); + }; + $app->extend('form.type.extensions', function ($extensions) { + $extensions[] = 'dummy.form.type.extension'; + + return $extensions; + }); + + $form = $app['form.factory'] + ->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType', array('image_path' => 'webPath')) + ->getForm(); + + $this->assertInstanceOf('Symfony\Component\Form\Form', $form); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid form type extension. The silex service "dummy.form.type.extension" does not exist. + */ + public function testNonExistentTypeExtensionService() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.type.extensions', function ($extensions) { + $extensions[] = 'dummy.form.type.extension'; + + return $extensions; + }); + + $app['form.factory'] + ->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('dummy', 'dummy.form.type') + ->getForm(); + } + + public function testFormServiceProviderWillLoadTypeGuessers() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.type.guessers', function ($guessers) { + $guessers[] = new FormTypeGuesserChain(array()); + + return $guessers; + }); + + $this->assertInstanceOf('Symfony\Component\Form\FormFactory', $app['form.factory']); + } + + public function testFormServiceProviderWillLoadTypeGuessersServices() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app['dummy.form.type.guesser'] = function () { + return new FormTypeGuesserChain(array()); + }; + $app->extend('form.type.guessers', function ($guessers) { + $guessers[] = 'dummy.form.type.guesser'; + + return $guessers; + }); + + $this->assertInstanceOf('Symfony\Component\Form\FormFactory', $app['form.factory']); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid form type guesser. The silex service "dummy.form.type.guesser" does not exist. + */ + public function testNonExistentTypeGuesserService() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.type.guessers', function ($extensions) { + $extensions[] = 'dummy.form.type.guesser'; + + return $extensions; + }); + + $factory = $app['form.factory']; + } + + public function testFormServiceProviderWillUseTranslatorIfAvailable() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + $app->register(new TranslationServiceProvider()); + $app['translator.domains'] = array( + 'messages' => array( + 'de' => array( + 'The CSRF token is invalid. Please try to resubmit the form.' => 'German translation', + ), + ), + ); + $app['locale'] = 'de'; + + $app['csrf.token_manager'] = function () { + return $this->getMockBuilder('Symfony\Component\Security\Csrf\CsrfTokenManagerInterface')->getMock(); + }; + + $form = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->getForm(); + + $form->handleRequest($req = Request::create('/', 'POST', array('form' => array( + '_token' => 'the wrong token', + )))); + + $this->assertFalse($form->isValid()); + $r = new \ReflectionMethod($form, 'getErrors'); + if (!$r->getNumberOfParameters()) { + $this->assertContains('ERROR: German translation', $form->getErrorsAsString()); + } else { + // as of 2.5 + $this->assertContains('ERROR: German translation', (string) $form->getErrors(true, false)); + } + } + + public function testFormServiceProviderWillNotAddNonexistentTranslationFiles() + { + $app = new Application(array( + 'locale' => 'nonexistent', + )); + + $app->register(new FormServiceProvider()); + $app->register(new ValidatorServiceProvider()); + $app->register(new TranslationServiceProvider(), array( + 'locale_fallbacks' => array(), + )); + + $app['form.factory']; + $translator = $app['translator']; + + try { + $translator->trans('test'); + } catch (NotFoundResourceException $e) { + $this->fail('Form factory should not add a translation resource that does not exist'); + } + } + + public function testFormCsrf() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $app->register(new SessionServiceProvider()); + $app->register(new CsrfServiceProvider()); + $app['session.test'] = true; + + $form = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array())->getForm(); + + $this->assertTrue(isset($form->createView()['_token'])); + } + + public function testUserExtensionCanConfigureDefaultExtensions() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $app->register(new SessionServiceProvider()); + $app->register(new CsrfServiceProvider()); + $app['session.test'] = true; + + $app->extend('form.type.extensions', function ($extensions) { + $extensions[] = new FormServiceProviderTest\DisableCsrfExtension(); + + return $extensions; + }); + $form = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array())->getForm(); + + $this->assertFalse($form->getConfig()->getOption('csrf_protection')); + } +} + +if (!class_exists('Symfony\Component\Form\Deprecated\FormEvents')) { + class DummyFormType extends AbstractType + { + } +} else { + // FormTypeInterface::getName() is needed by the form component 2.8.x + class DummyFormType extends AbstractType + { + /** + * @return string The name of this type + */ + public function getName() + { + return 'dummy'; + } + } +} + +if (method_exists('Symfony\Component\Form\AbstractType', 'configureOptions')) { + class DummyFormTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return 'Symfony\Component\Form\Extension\Core\Type\FileType'; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefined(array('image_path')); + } + } +} else { + class DummyFormTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return 'Symfony\Component\Form\Extension\Core\Type\FileType'; + } + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + if (!method_exists($resolver, 'setDefined')) { + $resolver->setOptional(array('image_path')); + } else { + $resolver->setDefined(array('image_path')); + } + } + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest/DisableCsrfExtension.php b/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest/DisableCsrfExtension.php new file mode 100644 index 00000000..8c82237d --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest/DisableCsrfExtension.php @@ -0,0 +1,22 @@ +setDefaults(array( + 'csrf_protection' => false, + )); + } + + public function getExtendedType() + { + return FormType::class; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/HttpCacheServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/HttpCacheServiceProviderTest.php new file mode 100644 index 00000000..93da4fe9 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/HttpCacheServiceProviderTest.php @@ -0,0 +1,80 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\HttpCacheServiceProvider; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * HttpCacheProvider test cases. + * + * @author Igor Wiedler + */ +class HttpCacheServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testRegister() + { + $app = new Application(); + + $app->register(new HttpCacheServiceProvider(), array( + 'http_cache.cache_dir' => sys_get_temp_dir().'/silex_http_cache_'.uniqid(), + )); + + $this->assertInstanceOf('Silex\Provider\HttpCache\HttpCache', $app['http_cache']); + + return $app; + } + + /** + * @depends testRegister + */ + public function testRunCallsShutdown($app) + { + $finished = false; + + $app->finish(function () use (&$finished) { + $finished = true; + }); + + $app->get('/', function () use ($app) { + return new UnsendableResponse('will do something after finish'); + }); + + $request = Request::create('/'); + $app['http_cache']->run($request); + + $this->assertTrue($finished); + } + + public function testDebugDefaultsToThatOfApp() + { + $app = new Application(); + + $app->register(new HttpCacheServiceProvider(), array( + 'http_cache.cache_dir' => sys_get_temp_dir().'/silex_http_cache_'.uniqid(), + )); + + $app['debug'] = true; + $app['http_cache']; + $this->assertTrue($app['http_cache.options']['debug']); + } +} + +class UnsendableResponse extends Response +{ + public function send() + { + // do nothing + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/HttpFragmentServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/HttpFragmentServiceProviderTest.php new file mode 100644 index 00000000..1a7620c1 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/HttpFragmentServiceProviderTest.php @@ -0,0 +1,48 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\HttpCacheServiceProvider; +use Silex\Provider\HttpFragmentServiceProvider; +use Silex\Provider\TwigServiceProvider; +use Symfony\Component\HttpFoundation\Request; + +class HttpFragmentServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testRenderFunction() + { + $app = new Application(); + unset($app['exception_handler']); + + $app->register(new HttpFragmentServiceProvider()); + $app->register(new HttpCacheServiceProvider(), array('http_cache.cache_dir' => sys_get_temp_dir())); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array( + 'hello' => '{{ render("/foo") }}{{ render_esi("/foo") }}{{ render_hinclude("/foo") }}', + 'foo' => 'foo', + ), + )); + + $app->get('/hello', function () use ($app) { + return $app['twig']->render('hello'); + }); + + $app->get('/foo', function () use ($app) { + return $app['twig']->render('foo'); + }); + + $response = $app['http_cache']->handle(Request::create('/hello')); + + $this->assertEquals('foofoo', $response->getContent()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/MonologServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/MonologServiceProviderTest.php new file mode 100644 index 00000000..9d8c260a --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/MonologServiceProviderTest.php @@ -0,0 +1,256 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Monolog\Formatter\JsonFormatter; +use Monolog\Handler\TestHandler; +use Monolog\Logger; +use Silex\Application; +use Silex\Provider\MonologServiceProvider; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Kernel; + +/** + * MonologProvider test cases. + * + * @author Igor Wiedler + */ +class MonologServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + private $currErrorHandler; + + protected function setUp() + { + $this->currErrorHandler = set_error_handler('var_dump'); + restore_error_handler(); + } + + protected function tearDown() + { + set_error_handler($this->currErrorHandler); + } + + public function testRequestLogging() + { + $app = $this->getApplication(); + + $app->get('/foo', function () use ($app) { + return 'foo'; + }); + + $this->assertFalse($app['monolog.handler']->hasInfoRecords()); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertTrue($app['monolog.handler']->hasDebug('> GET /foo')); + $this->assertTrue($app['monolog.handler']->hasDebug('< 200')); + + $records = $app['monolog.handler']->getRecords(); + if (Kernel::VERSION_ID < 30100) { + $this->assertContains('Matched route "GET_foo"', $records[0]['message']); + } else { + $this->assertContains('Matched route "{route}".', $records[0]['message']); + $this->assertSame('GET_foo', $records[0]['context']['route']); + } + } + + public function testManualLogging() + { + $app = $this->getApplication(); + + $app->get('/log', function () use ($app) { + $app['monolog']->addDebug('logging a message'); + }); + + $this->assertFalse($app['monolog.handler']->hasDebugRecords()); + + $request = Request::create('/log'); + $app->handle($request); + + $this->assertTrue($app['monolog.handler']->hasDebug('logging a message')); + } + + public function testOverrideFormatter() + { + $app = new Application(); + + $app->register(new MonologServiceProvider(), array( + 'monolog.formatter' => new JsonFormatter(), + 'monolog.logfile' => 'php://memory', + )); + + $this->assertInstanceOf('Monolog\Formatter\JsonFormatter', $app['monolog.handler']->getFormatter()); + } + + public function testErrorLogging() + { + $app = $this->getApplication(); + + $app->error(function (\Exception $e) { + return 'error handled'; + }); + + /* + * Simulate 404, logged to error level + */ + $this->assertFalse($app['monolog.handler']->hasErrorRecords()); + + $request = Request::create('/error'); + $app->handle($request); + + $pattern = "#Symfony\\\\Component\\\\HttpKernel\\\\Exception\\\\NotFoundHttpException: No route found for \"GET /error\" \(uncaught exception\) at .* line \d+#"; + $this->assertMatchingRecord($pattern, Logger::ERROR, $app['monolog.handler']); + + /* + * Simulate unhandled exception, logged to critical + */ + $app->get('/error', function () { + throw new \RuntimeException('very bad error'); + }); + + $this->assertFalse($app['monolog.handler']->hasCriticalRecords()); + + $request = Request::create('/error'); + $app->handle($request); + + $pattern = "#RuntimeException: very bad error \(uncaught exception\) at .* line \d+#"; + $this->assertMatchingRecord($pattern, Logger::CRITICAL, $app['monolog.handler']); + } + + public function testRedirectLogging() + { + $app = $this->getApplication(); + + $app->get('/foo', function () use ($app) { + return new RedirectResponse('/bar', 302); + }); + + $this->assertFalse($app['monolog.handler']->hasInfoRecords()); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertTrue($app['monolog.handler']->hasDebug('< 302 /bar')); + } + + public function testErrorLoggingGivesWayToSecurityExceptionHandling() + { + $app = $this->getApplication(); + $app['monolog.level'] = Logger::ERROR; + + $app->register(new \Silex\Provider\SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'admin' => array( + 'pattern' => '^/admin', + 'http' => true, + 'users' => array(), + ), + ), + )); + + $app->get('/admin', function () { + return 'SECURE!'; + }); + + $request = Request::create('/admin'); + $app->run($request); + + $this->assertEmpty($app['monolog.handler']->getRecords(), 'Expected no logging to occur'); + } + + public function testStringErrorLevel() + { + $app = $this->getApplication(); + $app['monolog.level'] = 'info'; + + $this->assertSame(Logger::INFO, $app['monolog.handler']->getLevel()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Provided logging level 'foo' does not exist. Must be a valid monolog logging level. + */ + public function testNonExistentStringErrorLevel() + { + $app = $this->getApplication(); + $app['monolog.level'] = 'foo'; + + $app['monolog.handler']->getLevel(); + } + + public function testDisableListener() + { + $app = $this->getApplication(); + unset($app['monolog.listener']); + + $app->handle(Request::create('/404')); + + $this->assertEmpty($app['monolog.handler']->getRecords(), 'Expected no logging to occur'); + } + + public function testExceptionFiltering() + { + $app = new Application(); + $app->get('/foo', function () use ($app) { + throw new NotFoundHttpException(); + }); + + $level = Logger::ERROR; + $app->register(new MonologServiceProvider(), array( + 'monolog.exception.logger_filter' => $app->protect(function () { + return Logger::DEBUG; + }), + 'monolog.handler' => function () use ($app) { + return new TestHandler($app['monolog.level']); + }, + 'monolog.level' => $level, + 'monolog.logfile' => 'php://memory', + )); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertCount(0, $app['monolog.handler']->getRecords(), 'Expected no logging to occur'); + } + + protected function assertMatchingRecord($pattern, $level, TestHandler $handler) + { + $found = false; + $records = $handler->getRecords(); + foreach ($records as $record) { + if (preg_match($pattern, $record['message']) && $record['level'] == $level) { + $found = true; + continue; + } + } + $this->assertTrue($found, "Trying to find record matching $pattern with level $level"); + } + + protected function getApplication() + { + $app = new Application(); + + $app->register(new MonologServiceProvider(), array( + 'monolog.handler' => function () use ($app) { + $level = MonologServiceProvider::translateLevel($app['monolog.level']); + + return new TestHandler($level); + }, + 'monolog.logfile' => 'php://memory', + )); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/RememberMeServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/RememberMeServiceProviderTest.php new file mode 100644 index 00000000..b027497c --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/RememberMeServiceProviderTest.php @@ -0,0 +1,107 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\WebTestCase; +use Silex\Provider\RememberMeServiceProvider; +use Silex\Provider\SecurityServiceProvider; +use Silex\Provider\SessionServiceProvider; +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\Security\Http\SecurityEvents; + +/** + * SecurityServiceProvider. + * + * @author Fabien Potencier + */ +class RememberMeServiceProviderTest extends WebTestCase +{ + public function testRememberMeAuthentication() + { + $app = $this->createApplication(); + + $interactiveLogin = new InteractiveLoginTriggered(); + $app->on(SecurityEvents::INTERACTIVE_LOGIN, array($interactiveLogin, 'onInteractiveLogin')); + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertFalse($interactiveLogin->triggered, 'The interactive login has not been triggered yet'); + $client->request('post', '/login_check', array('_username' => 'fabien', '_password' => 'foo', '_remember_me' => 'true')); + $client->followRedirect(); + $this->assertEquals('AUTHENTICATED_FULLY', $client->getResponse()->getContent()); + $this->assertTrue($interactiveLogin->triggered, 'The interactive login has been triggered'); + + $this->assertNotNull($client->getCookiejar()->get('REMEMBERME'), 'The REMEMBERME cookie is set'); + $event = false; + + $client->getCookiejar()->expire('MOCKSESSID'); + + $client->request('get', '/'); + $this->assertEquals('AUTHENTICATED_REMEMBERED', $client->getResponse()->getContent()); + $this->assertTrue($interactiveLogin->triggered, 'The interactive login has been triggered'); + + $client->request('get', '/logout'); + $client->followRedirect(); + + $this->assertNull($client->getCookiejar()->get('REMEMBERME'), 'The REMEMBERME cookie has been removed'); + } + + public function createApplication($authenticationMethod = 'form') + { + $app = new Application(); + + $app['debug'] = true; + unset($app['exception_handler']); + + $app->register(new SessionServiceProvider(), array( + 'session.test' => true, + )); + $app->register(new SecurityServiceProvider()); + $app->register(new RememberMeServiceProvider()); + + $app['security.firewalls'] = array( + 'http-auth' => array( + 'pattern' => '^.*$', + 'form' => true, + 'remember_me' => array(), + 'logout' => true, + 'users' => array( + 'fabien' => array('ROLE_USER', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + ), + ), + ); + + $app->get('/', function () use ($app) { + if ($app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_FULLY')) { + return 'AUTHENTICATED_FULLY'; + } elseif ($app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_REMEMBERED')) { + return 'AUTHENTICATED_REMEMBERED'; + } else { + return 'AUTHENTICATED_ANONYMOUSLY'; + } + }); + + return $app; + } +} + +class InteractiveLoginTriggered +{ + public $triggered = false; + + public function onInteractiveLogin() + { + $this->triggered = true; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/RoutingServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/RoutingServiceProviderTest.php new file mode 100644 index 00000000..da2ca78c --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/RoutingServiceProviderTest.php @@ -0,0 +1,121 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Pimple\Container; +use Silex\Application; +use Silex\Provider\RoutingServiceProvider; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * RoutingProvider test cases. + * + * @author Igor Wiedler + */ +class RoutingServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testRegister() + { + $app = new Application(); + + $app->get('/hello/{name}', function ($name) {}) + ->bind('hello'); + + $app->get('/', function () {}); + + $request = Request::create('/'); + $app->handle($request); + + $this->assertInstanceOf('Symfony\Component\Routing\Generator\UrlGenerator', $app['url_generator']); + } + + public function testUrlGeneration() + { + $app = new Application(); + + $app->get('/hello/{name}', function ($name) {}) + ->bind('hello'); + + $app->get('/', function () use ($app) { + return $app['url_generator']->generate('hello', array('name' => 'john')); + }); + + $request = Request::create('/'); + $response = $app->handle($request); + + $this->assertEquals('/hello/john', $response->getContent()); + } + + public function testAbsoluteUrlGeneration() + { + $app = new Application(); + + $app->get('/hello/{name}', function ($name) {}) + ->bind('hello'); + + $app->get('/', function () use ($app) { + return $app['url_generator']->generate('hello', array('name' => 'john'), UrlGeneratorInterface::ABSOLUTE_URL); + }); + + $request = Request::create('https://localhost:81/'); + $response = $app->handle($request); + + $this->assertEquals('https://localhost:81/hello/john', $response->getContent()); + } + + public function testUrlGenerationWithHttp() + { + $app = new Application(); + + $app->get('/insecure', function () {}) + ->bind('insecure_page') + ->requireHttp(); + + $app->get('/', function () use ($app) { + return $app['url_generator']->generate('insecure_page'); + }); + + $request = Request::create('https://localhost/'); + $response = $app->handle($request); + + $this->assertEquals('http://localhost/insecure', $response->getContent()); + } + + public function testUrlGenerationWithHttps() + { + $app = new Application(); + + $app->get('/secure', function () {}) + ->bind('secure_page') + ->requireHttps(); + + $app->get('/', function () use ($app) { + return $app['url_generator']->generate('secure_page'); + }); + + $request = Request::create('http://localhost/'); + $response = $app->handle($request); + + $this->assertEquals('https://localhost/secure', $response->getContent()); + } + + public function testControllersFactory() + { + $app = new Container(); + $app->register(new RoutingServiceProvider()); + $coll = $app['controllers_factory']; + $coll->mount('/blog', function ($blog) { + $this->assertInstanceOf('Silex\ControllerCollection', $blog); + }); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest.php new file mode 100644 index 00000000..3436c53b --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest.php @@ -0,0 +1,491 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\WebTestCase; +use Silex\Provider\SecurityServiceProvider; +use Silex\Provider\SessionServiceProvider; +use Silex\Provider\ValidatorServiceProvider; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\HttpFoundation\Request; + +/** + * SecurityServiceProvider. + * + * @author Fabien Potencier + */ +class SecurityServiceProviderTest extends WebTestCase +{ + /** + * @expectedException \LogicException + */ + public function testWrongAuthenticationType() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'wrong' => array( + 'foobar' => true, + 'users' => array(), + ), + ), + )); + $app->get('/', function () {}); + $app->handle(Request::create('/')); + } + + public function testFormAuthentication() + { + $app = $this->createApplication('form'); + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertEquals('ANONYMOUS', $client->getResponse()->getContent()); + + $client->request('post', '/login_check', array('_username' => 'fabien', '_password' => 'bar')); + $this->assertContains('Bad credentials', $app['security.last_error']($client->getRequest())); + // hack to re-close the session as the previous assertions re-opens it + $client->getRequest()->getSession()->save(); + + $client->request('post', '/login_check', array('_username' => 'fabien', '_password' => 'foo')); + $this->assertEquals('', $app['security.last_error']($client->getRequest())); + $client->getRequest()->getSession()->save(); + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + $this->assertEquals('http://localhost/', $client->getResponse()->getTargetUrl()); + + $client->request('get', '/'); + $this->assertEquals('fabienAUTHENTICATED', $client->getResponse()->getContent()); + $client->request('get', '/admin'); + $this->assertEquals(403, $client->getResponse()->getStatusCode()); + + $client->request('get', '/logout'); + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + $this->assertEquals('http://localhost/', $client->getResponse()->getTargetUrl()); + + $client->request('get', '/'); + $this->assertEquals('ANONYMOUS', $client->getResponse()->getContent()); + + $client->request('get', '/admin'); + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + $this->assertEquals('http://localhost/login', $client->getResponse()->getTargetUrl()); + + $client->request('post', '/login_check', array('_username' => 'admin', '_password' => 'foo')); + $this->assertEquals('', $app['security.last_error']($client->getRequest())); + $client->getRequest()->getSession()->save(); + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + $this->assertEquals('http://localhost/admin', $client->getResponse()->getTargetUrl()); + + $client->request('get', '/'); + $this->assertEquals('adminAUTHENTICATEDADMIN', $client->getResponse()->getContent()); + $client->request('get', '/admin'); + $this->assertEquals('admin', $client->getResponse()->getContent()); + } + + public function testHttpAuthentication() + { + $app = $this->createApplication('http'); + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertEquals(401, $client->getResponse()->getStatusCode()); + $this->assertEquals('Basic realm="Secured"', $client->getResponse()->headers->get('www-authenticate')); + + $client->request('get', '/', array(), array(), array('PHP_AUTH_USER' => 'dennis', 'PHP_AUTH_PW' => 'foo')); + $this->assertEquals('dennisAUTHENTICATED', $client->getResponse()->getContent()); + $client->request('get', '/admin'); + $this->assertEquals(403, $client->getResponse()->getStatusCode()); + + $client->restart(); + + $client->request('get', '/'); + $this->assertEquals(401, $client->getResponse()->getStatusCode()); + $this->assertEquals('Basic realm="Secured"', $client->getResponse()->headers->get('www-authenticate')); + + $client->request('get', '/', array(), array(), array('PHP_AUTH_USER' => 'admin', 'PHP_AUTH_PW' => 'foo')); + $this->assertEquals('adminAUTHENTICATEDADMIN', $client->getResponse()->getContent()); + $client->request('get', '/admin'); + $this->assertEquals('admin', $client->getResponse()->getContent()); + } + + public function testGuardAuthentication() + { + $app = $this->createApplication('guard'); + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertEquals(401, $client->getResponse()->getStatusCode(), 'The entry point is configured'); + $this->assertEquals('{"message":"Authentication Required"}', $client->getResponse()->getContent()); + + $client->request('get', '/', array(), array(), array('HTTP_X_AUTH_TOKEN' => 'lili:not the secret')); + $this->assertEquals(403, $client->getResponse()->getStatusCode(), 'User not found'); + $this->assertEquals('{"message":"Username could not be found."}', $client->getResponse()->getContent()); + + $client->request('get', '/', array(), array(), array('HTTP_X_AUTH_TOKEN' => 'victoria:not the secret')); + $this->assertEquals(403, $client->getResponse()->getStatusCode(), 'Invalid credentials'); + $this->assertEquals('{"message":"Invalid credentials."}', $client->getResponse()->getContent()); + + $client->request('get', '/', array(), array(), array('HTTP_X_AUTH_TOKEN' => 'victoria:victoriasecret')); + $this->assertEquals('victoria', $client->getResponse()->getContent()); + } + + public function testUserPasswordValidatorIsRegistered() + { + $app = new Application(); + + $app->register(new ValidatorServiceProvider()); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'admin' => array( + 'pattern' => '^/admin', + 'http' => true, + 'users' => array( + 'admin' => array('ROLE_ADMIN', '513aeb0121909'), + ), + ), + ), + )); + + $app->boot(); + + $this->assertInstanceOf('Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator', $app['security.validator.user_password_validator']); + } + + public function testExposedExceptions() + { + $app = $this->createApplication('form'); + $app['security.hide_user_not_found'] = false; + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertEquals('ANONYMOUS', $client->getResponse()->getContent()); + + $client->request('post', '/login_check', array('_username' => 'fabien', '_password' => 'bar')); + $this->assertEquals('The presented password is invalid.', $app['security.last_error']($client->getRequest())); + $client->getRequest()->getSession()->save(); + + $client->request('post', '/login_check', array('_username' => 'unknown', '_password' => 'bar')); + $this->assertEquals('Username "unknown" does not exist.', $app['security.last_error']($client->getRequest())); + $client->getRequest()->getSession()->save(); + } + + public function testFakeRoutesAreSerializable() + { + $app = new Application(); + + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'admin' => array( + 'logout' => true, + ), + ), + )); + + $app->boot(); + $app->flush(); + + $this->assertCount(1, unserialize(serialize($app['routes']))); + } + + public function testFirewallWithMethod() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'pattern' => '/', + 'http' => true, + 'methods' => array('POST'), + ), + ), + )); + $app->match('/', function () { return 'foo'; }) + ->method('POST|GET'); + + $request = Request::create('/', 'GET'); + $response = $app->handle($request); + $this->assertEquals(200, $response->getStatusCode()); + + $request = Request::create('/', 'POST'); + $response = $app->handle($request); + $this->assertEquals(401, $response->getStatusCode()); + } + + public function testFirewallWithHost() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'pattern' => '/', + 'http' => true, + 'hosts' => 'localhost2', + ), + ), + )); + $app->get('/', function () { return 'foo'; }) + ->host('localhost2'); + + $app->get('/', function () { return 'foo'; }) + ->host('localhost1'); + + $request = Request::create('http://localhost2/'); + $response = $app->handle($request); + $this->assertEquals(401, $response->getStatusCode()); + + $request = Request::create('http://localhost1/'); + $response = $app->handle($request); + $this->assertEquals(200, $response->getStatusCode()); + } + + public function testUser() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + 'users' => array( + 'fabien' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + ), + ), + ), + )); + $app->get('/', function () { return 'foo'; }); + + $request = Request::create('/'); + $app->handle($request); + $this->assertNull($app['user']); + + $request->headers->set('PHP_AUTH_USER', 'fabien'); + $request->headers->set('PHP_AUTH_PW', 'foo'); + $app->handle($request); + $this->assertInstanceOf('Symfony\Component\Security\Core\User\UserInterface', $app['user']); + $this->assertEquals('fabien', $app['user']->getUsername()); + } + + public function testUserWithNoToken() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + ), + ), + )); + + $request = Request::create('/'); + + $app->get('/', function () { return 'foo'; }); + $app->handle($request); + $this->assertNull($app['user']); + } + + public function testUserWithInvalidUser() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + ), + ), + )); + + $request = Request::create('/'); + $app->boot(); + $app['security.token_storage']->setToken(new UsernamePasswordToken('foo', 'foo', 'foo')); + + $app->get('/', function () { return 'foo'; }); + $app->handle($request); + $this->assertNull($app['user']); + } + + public function testAccessRulePathArray() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + ), + ), + 'security.access_rules' => array( + array(array( + 'path' => '^/admin', + ), 'ROLE_ADMIN'), + ), + )); + + $request = Request::create('/admin'); + $app->boot(); + $accessMap = $app['security.access_map']; + $this->assertEquals($accessMap->getPatterns($request), array( + array('ROLE_ADMIN'), + '', + )); + } + + public function createApplication($authenticationMethod = 'form') + { + $app = new Application(); + $app->register(new SessionServiceProvider()); + + $app = call_user_func(array($this, 'add'.ucfirst($authenticationMethod).'Authentication'), $app); + + $app['session.test'] = true; + + return $app; + } + + private function addFormAuthentication($app) + { + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'login' => array( + 'pattern' => '^/login$', + ), + 'default' => array( + 'pattern' => '^.*$', + 'anonymous' => true, + 'form' => array( + 'require_previous_session' => false, + ), + 'logout' => true, + 'users' => array( + // password is foo + 'fabien' => array('ROLE_USER', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + 'admin' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + ), + ), + ), + 'security.access_rules' => array( + array('^/admin', 'ROLE_ADMIN'), + ), + 'security.role_hierarchy' => array( + 'ROLE_ADMIN' => array('ROLE_USER'), + ), + )); + + $app->get('/login', function (Request $request) use ($app) { + $app['session']->start(); + + return $app['security.last_error']($request); + }); + + $app->get('/', function () use ($app) { + $user = $app['security.token_storage']->getToken()->getUser(); + + $content = is_object($user) ? $user->getUsername() : 'ANONYMOUS'; + + if ($app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_FULLY')) { + $content .= 'AUTHENTICATED'; + } + + if ($app['security.authorization_checker']->isGranted('ROLE_ADMIN')) { + $content .= 'ADMIN'; + } + + return $content; + }); + + $app->get('/admin', function () use ($app) { + return 'admin'; + }); + + return $app; + } + + private function addHttpAuthentication($app) + { + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'http-auth' => array( + 'pattern' => '^.*$', + 'http' => true, + 'users' => array( + // password is foo + 'dennis' => array('ROLE_USER', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + 'admin' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + ), + ), + ), + 'security.access_rules' => array( + array('^/admin', 'ROLE_ADMIN'), + ), + 'security.role_hierarchy' => array( + 'ROLE_ADMIN' => array('ROLE_USER'), + ), + )); + + $app->get('/', function () use ($app) { + $user = $app['security.token_storage']->getToken()->getUser(); + $content = is_object($user) ? $user->getUsername() : 'ANONYMOUS'; + + if ($app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_FULLY')) { + $content .= 'AUTHENTICATED'; + } + + if ($app['security.authorization_checker']->isGranted('ROLE_ADMIN')) { + $content .= 'ADMIN'; + } + + return $content; + }); + + $app->get('/admin', function () use ($app) { + return 'admin'; + }); + + return $app; + } + + private function addGuardAuthentication($app) + { + $app['app.authenticator.token'] = function ($app) { + return new SecurityServiceProviderTest\TokenAuthenticator($app); + }; + + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'guard' => array( + 'pattern' => '^.*$', + 'form' => true, + 'guard' => array( + 'authenticators' => array( + 'app.authenticator.token', + ), + ), + 'users' => array( + 'victoria' => array('ROLE_USER', 'victoriasecret'), + ), + ), + ), + )); + + $app->get('/', function () use ($app) { + $user = $app['security.token_storage']->getToken()->getUser(); + + $content = is_object($user) ? $user->getUsername() : 'ANONYMOUS'; + + return $content; + })->bind('homepage'); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest/TokenAuthenticator.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest/TokenAuthenticator.php new file mode 100644 index 00000000..c569428b --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest/TokenAuthenticator.php @@ -0,0 +1,79 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider\SecurityServiceProviderTest; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +/** + * This class is used to test "guard" authentication with the SecurityServiceProvider. + */ +class TokenAuthenticator extends AbstractGuardAuthenticator +{ + public function getCredentials(Request $request) + { + if (!$token = $request->headers->get('X-AUTH-TOKEN')) { + return; + } + + list($username, $secret) = explode(':', $token); + + return array( + 'username' => $username, + 'secret' => $secret, + ); + } + + public function getUser($credentials, UserProviderInterface $userProvider) + { + return $userProvider->loadUserByUsername($credentials['username']); + } + + public function checkCredentials($credentials, UserInterface $user) + { + // This is not a safe way of validating a password. + return $user->getPassword() === $credentials['secret']; + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) + { + return; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception) + { + $data = array( + 'message' => strtr($exception->getMessageKey(), $exception->getMessageData()), + ); + + return new JsonResponse($data, 403); + } + + public function start(Request $request, AuthenticationException $authException = null) + { + $data = array( + 'message' => 'Authentication Required', + ); + + return new JsonResponse($data, 401); + } + + public function supportsRememberMe() + { + return false; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SerializerServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SerializerServiceProviderTest.php new file mode 100644 index 00000000..0b143f5f --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SerializerServiceProviderTest.php @@ -0,0 +1,36 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\SerializerServiceProvider; + +/** + * SerializerServiceProvider test cases. + * + * @author Fabien Potencier + */ +class SerializerServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testRegister() + { + $app = new Application(); + + $app->register(new SerializerServiceProvider()); + + $this->assertInstanceOf("Symfony\Component\Serializer\Serializer", $app['serializer']); + $this->assertTrue($app['serializer']->supportsEncoding('xml')); + $this->assertTrue($app['serializer']->supportsEncoding('json')); + $this->assertTrue($app['serializer']->supportsDecoding('xml')); + $this->assertTrue($app['serializer']->supportsDecoding('json')); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SessionServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SessionServiceProviderTest.php new file mode 100644 index 00000000..fb7ae0cf --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SessionServiceProviderTest.php @@ -0,0 +1,126 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\WebTestCase; +use Silex\Provider\SessionServiceProvider; +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\HttpFoundation\Session; + +/** + * SessionProvider test cases. + * + * @author Igor Wiedler + * @author Fabien Potencier + */ +class SessionServiceProviderTest extends WebTestCase +{ + public function testRegister() + { + /* + * Smoke test + */ + $defaultStorage = $this->app['session.storage.native']; + + $client = $this->createClient(); + + $client->request('get', '/login'); + $this->assertEquals('Logged in successfully.', $client->getResponse()->getContent()); + + $client->request('get', '/account'); + $this->assertEquals('This is your account.', $client->getResponse()->getContent()); + + $client->request('get', '/logout'); + $this->assertEquals('Logged out successfully.', $client->getResponse()->getContent()); + + $client->request('get', '/account'); + $this->assertEquals('You are not logged in.', $client->getResponse()->getContent()); + } + + public function createApplication() + { + $app = new Application(); + + $app->register(new SessionServiceProvider(), array( + 'session.test' => true, + )); + + $app->get('/login', function () use ($app) { + $app['session']->set('logged_in', true); + + return 'Logged in successfully.'; + }); + + $app->get('/account', function () use ($app) { + if (!$app['session']->get('logged_in')) { + return 'You are not logged in.'; + } + + return 'This is your account.'; + }); + + $app->get('/logout', function () use ($app) { + $app['session']->invalidate(); + + return 'Logged out successfully.'; + }); + + return $app; + } + + public function testWithRoutesThatDoesNotUseSession() + { + $app = new Application(); + + $app->register(new SessionServiceProvider(), array( + 'session.test' => true, + )); + + $app->get('/', function () { + return 'A welcome page.'; + }); + + $app->get('/robots.txt', function () { + return 'Informations for robots.'; + }); + + $app['debug'] = true; + unset($app['exception_handler']); + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertEquals('A welcome page.', $client->getResponse()->getContent()); + + $client->request('get', '/robots.txt'); + $this->assertEquals('Informations for robots.', $client->getResponse()->getContent()); + } + + public function testSessionRegister() + { + $app = new Application(); + + $attrs = new Session\Attribute\AttributeBag(); + $flash = new Session\Flash\FlashBag(); + $app->register(new SessionServiceProvider(), array( + 'session.attribute_bag' => $attrs, + 'session.flash_bag' => $flash, + 'session.test' => true, + )); + + $session = $app['session']; + + $this->assertSame($flash, $session->getBag('flashes')); + $this->assertSame($attrs, $session->getBag('attributes')); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SpoolStub.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SpoolStub.php new file mode 100644 index 00000000..c1eb2b0f --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SpoolStub.php @@ -0,0 +1,47 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +class SpoolStub implements \Swift_Spool +{ + private $messages = array(); + public $hasFlushed = false; + + public function getMessages() + { + return $this->messages; + } + + public function start() + { + } + + public function stop() + { + } + + public function isStarted() + { + return count($this->messages) > 0; + } + + public function queueMessage(\Swift_Mime_Message $message) + { + $this->messages[] = clone $message; + } + + public function flushQueue(\Swift_Transport $transport, &$failedRecipients = null) + { + $this->hasFlushed = true; + $this->messages = array(); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SwiftmailerServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SwiftmailerServiceProviderTest.php new file mode 100644 index 00000000..f9ddc4aa --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SwiftmailerServiceProviderTest.php @@ -0,0 +1,133 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\SwiftmailerServiceProvider; +use Symfony\Component\HttpFoundation\Request; + +class SwiftmailerServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testSwiftMailerServiceIsSwiftMailer() + { + $app = new Application(); + + $app->register(new SwiftmailerServiceProvider()); + $app->boot(); + + $this->assertInstanceOf('Swift_Mailer', $app['mailer']); + } + + public function testSwiftMailerIgnoresSpoolIfDisabled() + { + $app = new Application(); + + $app->register(new SwiftmailerServiceProvider()); + $app->boot(); + + $app['swiftmailer.use_spool'] = false; + + $app['swiftmailer.spooltransport'] = function () { + throw new \Exception('Should not be instantiated'); + }; + + $this->assertInstanceOf('Swift_Mailer', $app['mailer']); + } + + public function testSwiftMailerSendsMailsOnFinish() + { + $app = new Application(); + + $app->register(new SwiftmailerServiceProvider()); + $app->boot(); + + $app['swiftmailer.spool'] = function () { + return new SpoolStub(); + }; + + $app->get('/', function () use ($app) { + $app['mailer']->send(\Swift_Message::newInstance()); + }); + + $this->assertCount(0, $app['swiftmailer.spool']->getMessages()); + + $request = Request::create('/'); + $response = $app->handle($request); + $this->assertCount(1, $app['swiftmailer.spool']->getMessages()); + + $app->terminate($request, $response); + $this->assertTrue($app['swiftmailer.spool']->hasFlushed); + $this->assertCount(0, $app['swiftmailer.spool']->getMessages()); + } + + public function testSwiftMailerAvoidsFlushesIfMailerIsUnused() + { + $app = new Application(); + + $app->register(new SwiftmailerServiceProvider()); + $app->boot(); + + $app['swiftmailer.spool'] = function () { + return new SpoolStub(); + }; + + $app->get('/', function () use ($app) { }); + + $request = Request::create('/'); + $response = $app->handle($request); + $this->assertCount(0, $app['swiftmailer.spool']->getMessages()); + + $app->terminate($request, $response); + $this->assertFalse($app['swiftmailer.spool']->hasFlushed); + } + + public function testSwiftMailerSenderAddress() + { + $app = new Application(); + + $app->register(new SwiftmailerServiceProvider()); + $app->boot(); + + $app['swiftmailer.spool'] = function () { + return new SpoolStub(); + }; + + $app['swiftmailer.sender_address'] = 'foo@example.com'; + + $app['mailer']->send(\Swift_Message::newInstance()); + + $messages = $app['swiftmailer.spool']->getMessages(); + $this->assertCount(1, $messages); + + list($message) = $messages; + $this->assertEquals('foo@example.com', $message->getReturnPath()); + } + + public function testSwiftMailerPlugins() + { + $plugin = $this->getMockBuilder('Swift_Events_TransportChangeListener')->getMock(); + $plugin->expects($this->once())->method('beforeTransportStarted'); + + $app = new Application(); + $app->boot(); + + $app->register(new SwiftmailerServiceProvider()); + + $app['swiftmailer.plugins'] = function ($app) use ($plugin) { + return array($plugin); + }; + + $dispatcher = $app['swiftmailer.transport.eventdispatcher']; + $event = $dispatcher->createTransportChangeEvent(new \Swift_Transport_NullTransport($dispatcher)); + $dispatcher->dispatchEvent($event, 'beforeTransportStarted'); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/TranslationServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/TranslationServiceProviderTest.php new file mode 100644 index 00000000..6d893f98 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/TranslationServiceProviderTest.php @@ -0,0 +1,181 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\TranslationServiceProvider; +use Silex\Provider\LocaleServiceProvider; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * TranslationProvider test cases. + * + * @author Daniel Tschinder + */ +class TranslationServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @return Application + */ + protected function getPreparedApp() + { + $app = new Application(); + + $app->register(new LocaleServiceProvider()); + $app->register(new TranslationServiceProvider()); + $app['translator.domains'] = array( + 'messages' => array( + 'en' => array( + 'key1' => 'The translation', + 'key_only_english' => 'Foo', + 'key2' => 'One apple|%count% apples', + 'test' => array( + 'key' => 'It works', + ), + ), + 'de' => array( + 'key1' => 'The german translation', + 'key2' => 'One german apple|%count% german apples', + 'test' => array( + 'key' => 'It works in german', + ), + ), + ), + ); + + return $app; + } + + public function transChoiceProvider() + { + return array( + array('key2', 0, null, '0 apples'), + array('key2', 1, null, 'One apple'), + array('key2', 2, null, '2 apples'), + array('key2', 0, 'de', '0 german apples'), + array('key2', 1, 'de', 'One german apple'), + array('key2', 2, 'de', '2 german apples'), + array('key2', 0, 'ru', '0 apples'), // fallback + array('key2', 1, 'ru', 'One apple'), // fallback + array('key2', 2, 'ru', '2 apples'), // fallback + ); + } + + public function transProvider() + { + return array( + array('key1', null, 'The translation'), + array('key1', 'de', 'The german translation'), + array('key1', 'ru', 'The translation'), // fallback + array('test.key', null, 'It works'), + array('test.key', 'de', 'It works in german'), + array('test.key', 'ru', 'It works'), // fallback + ); + } + + /** + * @dataProvider transProvider + */ + public function testTransForDefaultLanguage($key, $locale, $expected) + { + $app = $this->getPreparedApp(); + + $result = $app['translator']->trans($key, array(), null, $locale); + + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider transChoiceProvider + */ + public function testTransChoiceForDefaultLanguage($key, $number, $locale, $expected) + { + $app = $this->getPreparedApp(); + + $result = $app['translator']->transChoice($key, $number, array('%count%' => $number), null, $locale); + $this->assertEquals($expected, $result); + } + + public function testFallbacks() + { + $app = $this->getPreparedApp(); + $app['locale_fallbacks'] = array('de', 'en'); + + // fallback to english + $result = $app['translator']->trans('key_only_english', array(), null, 'ru'); + $this->assertEquals('Foo', $result); + + // fallback to german + $result = $app['translator']->trans('key1', array(), null, 'ru'); + $this->assertEquals('The german translation', $result); + } + + public function testLocale() + { + $app = $this->getPreparedApp(); + $app->get('/', function () use ($app) { return $app['translator']->getLocale(); }); + $response = $app->handle(Request::create('/')); + $this->assertEquals('en', $response->getContent()); + + $app = $this->getPreparedApp(); + $app->get('/', function () use ($app) { return $app['translator']->getLocale(); }); + $request = Request::create('/'); + $request->setLocale('fr'); + $response = $app->handle($request); + $this->assertEquals('fr', $response->getContent()); + + $app = $this->getPreparedApp(); + $app->get('/{_locale}', function () use ($app) { return $app['translator']->getLocale(); }); + $response = $app->handle(Request::create('/es')); + $this->assertEquals('es', $response->getContent()); + } + + public function testLocaleInSubRequests() + { + $app = $this->getPreparedApp(); + $app->get('/embed/{_locale}', function () use ($app) { return $app['translator']->getLocale(); }); + $app->get('/{_locale}', function () use ($app) { + return $app['translator']->getLocale(). + $app->handle(Request::create('/embed/es'), HttpKernelInterface::SUB_REQUEST)->getContent(). + $app['translator']->getLocale(); + }); + $response = $app->handle(Request::create('/fr')); + $this->assertEquals('fresfr', $response->getContent()); + + $app = $this->getPreparedApp(); + $app->get('/embed', function () use ($app) { return $app['translator']->getLocale(); }); + $app->get('/{_locale}', function () use ($app) { + return $app['translator']->getLocale(). + $app->handle(Request::create('/embed'), HttpKernelInterface::SUB_REQUEST)->getContent(). + $app['translator']->getLocale(); + }); + $response = $app->handle(Request::create('/fr')); + // locale in sub-request must be "en" as this is the value if the sub-request is converted to an ESI + $this->assertEquals('frenfr', $response->getContent()); + } + + public function testLocaleWithBefore() + { + $app = $this->getPreparedApp(); + $app->before(function (Request $request) { $request->setLocale('fr'); }, Application::EARLY_EVENT); + $app->get('/embed', function () use ($app) { return $app['translator']->getLocale(); }); + $app->get('/', function () use ($app) { + return $app['translator']->getLocale(). + $app->handle(Request::create('/embed'), HttpKernelInterface::SUB_REQUEST)->getContent(). + $app['translator']->getLocale(); + }); + $response = $app->handle(Request::create('/')); + // locale in sub-request is "en" as the before filter is only executed for the main request + $this->assertEquals('frenfr', $response->getContent()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/TwigServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/TwigServiceProviderTest.php new file mode 100644 index 00000000..501036c5 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/TwigServiceProviderTest.php @@ -0,0 +1,163 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Fig\Link\Link; +use Silex\Application; +use Silex\Provider\CsrfServiceProvider; +use Silex\Provider\FormServiceProvider; +use Silex\Provider\TwigServiceProvider; +use Silex\Provider\AssetServiceProvider; +use Symfony\Bridge\Twig\Extension\WebLinkExtension; +use Symfony\Component\HttpFoundation\Request; + +/** + * TwigProvider test cases. + * + * @author Igor Wiedler + */ +class TwigServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testRegisterAndRender() + { + $app = new Application(); + + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array('hello' => 'Hello {{ name }}!'), + )); + + $app->get('/hello/{name}', function ($name) use ($app) { + return $app['twig']->render('hello', array('name' => $name)); + }); + + $request = Request::create('/hello/john'); + $response = $app->handle($request); + $this->assertEquals('Hello john!', $response->getContent()); + } + + public function testLoaderPriority() + { + $app = new Application(); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array('foo' => 'foo'), + )); + $loader = $this->getMockBuilder('\Twig_LoaderInterface')->getMock(); + $loader->expects($this->never())->method('getSourceContext'); + $app['twig.loader.filesystem'] = function ($app) use ($loader) { + return $loader; + }; + $this->assertEquals('foo', $app['twig.loader']->getSourceContext('foo')->getCode()); + } + + public function testHttpFoundationIntegration() + { + $app = new Application(); + $app['request_stack']->push(Request::create('/dir1/dir2/file')); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array( + 'absolute' => '{{ absolute_url("foo.css") }}', + 'relative' => '{{ relative_path("/dir1/foo.css") }}', + ), + )); + + $this->assertEquals('http://localhost/dir1/dir2/foo.css', $app['twig']->render('absolute')); + $this->assertEquals('../foo.css', $app['twig']->render('relative')); + } + + public function testAssetIntegration() + { + $app = new Application(); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array('hello' => '{{ asset("/foo.css") }}'), + )); + $app->register(new AssetServiceProvider(), array( + 'assets.version' => 1, + )); + + $this->assertEquals('/foo.css?1', $app['twig']->render('hello')); + } + + public function testGlobalVariable() + { + $app = new Application(); + $app['request_stack']->push(Request::create('/?name=Fabien')); + + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array('hello' => '{{ global.request.get("name") }}'), + )); + + $this->assertEquals('Fabien', $app['twig']->render('hello')); + } + + public function testFormFactory() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $app->register(new CsrfServiceProvider()); + $app->register(new TwigServiceProvider()); + + $this->assertInstanceOf('Twig_Environment', $app['twig'], 'Service twig is created successful.'); + $this->assertInstanceOf('Symfony\Bridge\Twig\Form\TwigRendererEngine', $app['twig.form.engine'], 'Service twig.form.engine is created successful.'); + $this->assertInstanceOf('Symfony\Bridge\Twig\Form\TwigRenderer', $app['twig.form.renderer'], 'Service twig.form.renderer is created successful.'); + } + + public function testFormWithoutCsrf() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $app->register(new TwigServiceProvider()); + + $this->assertInstanceOf('Twig_Environment', $app['twig']); + } + + public function testFormatParameters() + { + $app = new Application(); + + $timezone = new \DateTimeZone('Europe/Paris'); + + $app->register(new TwigServiceProvider(), array( + 'twig.date.format' => 'Y-m-d', + 'twig.date.interval_format' => '%h hours', + 'twig.date.timezone' => $timezone, + 'twig.number_format.decimals' => 2, + 'twig.number_format.decimal_point' => ',', + 'twig.number_format.thousands_separator' => ' ', + )); + + $twig = $app['twig']; + + $this->assertSame(array('Y-m-d', '%h hours'), $twig->getExtension('Twig_Extension_Core')->getDateFormat()); + $this->assertSame($timezone, $twig->getExtension('Twig_Extension_Core')->getTimezone()); + $this->assertSame(array(2, ',', ' '), $twig->getExtension('Twig_Extension_Core')->getNumberFormat()); + } + + public function testWebLinkIntegration() + { + if (!class_exists(WebLinkExtension::class)) { + $this->markTestSkipped('Twig WebLink extension not available.'); + } + + $app = new Application(); + $app['request_stack']->push($request = Request::create('/')); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array( + 'preload' => '{{ preload("/foo.css") }}', + ), + )); + + $this->assertEquals('/foo.css', $app['twig']->render('preload')); + + $link = new Link('preload', '/foo.css'); + $this->assertEquals(array($link), array_values($request->attributes->get('_links')->getLinks())); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest.php new file mode 100644 index 00000000..a28ccf78 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest.php @@ -0,0 +1,194 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\TranslationServiceProvider; +use Silex\Provider\ValidatorServiceProvider; +use Silex\Provider\FormServiceProvider; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Validator\Constraints as Assert; +use Silex\Tests\Provider\ValidatorServiceProviderTest\Constraint\Custom; +use Silex\Tests\Provider\ValidatorServiceProviderTest\Constraint\CustomValidator; +use Symfony\Component\Validator\ValidatorInterface as LegacyValidatorInterface; +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * ValidatorServiceProvider. + * + * Javier Lopez + */ +class ValidatorServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testRegister() + { + $app = new Application(); + $app->register(new ValidatorServiceProvider()); + $app->register(new FormServiceProvider()); + + return $app; + } + + public function testRegisterWithCustomValidators() + { + $app = new Application(); + + $app['custom.validator'] = function () { + return new CustomValidator(); + }; + + $app->register(new ValidatorServiceProvider(), array( + 'validator.validator_service_ids' => array( + 'test.custom.validator' => 'custom.validator', + ), + )); + + return $app; + } + + /** + * @depends testRegisterWithCustomValidators + */ + public function testConstraintValidatorFactory($app) + { + $this->assertInstanceOf('Silex\Provider\Validator\ConstraintValidatorFactory', $app['validator.validator_factory']); + + $validator = $app['validator.validator_factory']->getInstance(new Custom()); + $this->assertInstanceOf('Silex\Tests\Provider\ValidatorServiceProviderTest\Constraint\CustomValidator', $validator); + } + + /** + * @depends testRegister + */ + public function testConstraintValidatorFactoryWithExpression($app) + { + $constraint = new Assert\Expression('true'); + $validator = $app['validator.validator_factory']->getInstance($constraint); + $this->assertInstanceOf('Symfony\Component\Validator\Constraints\ExpressionValidator', $validator); + } + + /** + * @depends testRegister + */ + public function testValidatorServiceIsAValidator($app) + { + $this->assertTrue($app['validator'] instanceof ValidatorInterface || $app['validator'] instanceof LegacyValidatorInterface ); + } + + /** + * @depends testRegister + * @dataProvider getTestValidatorConstraintProvider + */ + public function testValidatorConstraint($email, $isValid, $nbGlobalError, $nbEmailError, $app) + { + $constraints = new Assert\Collection(array( + 'email' => array( + new Assert\NotBlank(), + new Assert\Email(), + ), + )); + + $builder = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array(), array( + 'constraints' => $constraints, + )); + + $form = $builder + ->add('email', 'Symfony\Component\Form\Extension\Core\Type\EmailType', array('label' => 'Email')) + ->getForm() + ; + + $form->submit(array('email' => $email)); + + $this->assertEquals($isValid, $form->isValid()); + $this->assertEquals($nbGlobalError, count($form->getErrors())); + $this->assertEquals($nbEmailError, count($form->offsetGet('email')->getErrors())); + } + + public function testValidatorWillNotAddNonexistentTranslationFiles() + { + $app = new Application(array( + 'locale' => 'nonexistent', + )); + + $app->register(new ValidatorServiceProvider()); + $app->register(new TranslationServiceProvider(), array( + 'locale_fallbacks' => array(), + )); + + $app['validator']; + $translator = $app['translator']; + + try { + $translator->trans('test'); + } catch (NotFoundResourceException $e) { + $this->fail('Validator should not add a translation resource that does not exist'); + } + } + + public function getTestValidatorConstraintProvider() + { + // Email, form is valid, nb global error, nb email error + return array( + array('', false, 0, 1), + array('not an email', false, 0, 1), + array('email@sample.com', true, 0, 0), + ); + } + + /** + * @dataProvider getAddResourceData + */ + public function testAddResource($registerValidatorFirst) + { + $app = new Application(); + $app['locale'] = 'fr'; + + $app->register(new ValidatorServiceProvider()); + $app->register(new TranslationServiceProvider()); + $app['translator'] = $app->extend('translator', function ($translator, $app) { + $translator->addResource('array', array('This value should not be blank.' => 'Pas vide'), 'fr', 'validators'); + + return $translator; + }); + + if ($registerValidatorFirst) { + $app['validator']; + } + + $this->assertEquals('Pas vide', $app['translator']->trans('This value should not be blank.', array(), 'validators', 'fr')); + } + + public function getAddResourceData() + { + return array(array(false), array(true)); + } + + public function testAddResourceAlternate() + { + $app = new Application(); + $app['locale'] = 'fr'; + + $app->register(new ValidatorServiceProvider()); + $app->register(new TranslationServiceProvider()); + $app->factory($app->extend('translator.resources', function ($resources, $app) { + $resources = array_merge($resources, array( + array('array', array('This value should not be blank.' => 'Pas vide'), 'fr', 'validators'), + )); + + return $resources; + })); + + $app['validator']; + + $this->assertEquals('Pas vide', $app['translator']->trans('This value should not be blank.', array(), 'validators', 'fr')); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/Custom.php b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/Custom.php new file mode 100644 index 00000000..bef911aa --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/Custom.php @@ -0,0 +1,29 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider\ValidatorServiceProviderTest\Constraint; + +use Symfony\Component\Validator\Constraint; + +/** + * @author Alex Kalyvitis + */ +class Custom extends Constraint +{ + public $message = 'This field must be ...'; + public $table; + public $field; + + public function validatedBy() + { + return 'test.custom.validator'; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/CustomValidator.php b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/CustomValidator.php new file mode 100644 index 00000000..856927db --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/CustomValidator.php @@ -0,0 +1,32 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider\ValidatorServiceProviderTest\Constraint; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; + +/** + * @author Alex Kalyvitis + */ +class CustomValidator extends ConstraintValidator +{ + public function isValid($value, Constraint $constraint) + { + // Validate... + return true; + } + + public function validate($value, Constraint $constraint) + { + return $this->isValid($value, $constraint); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Route/SecurityRoute.php b/vendor/silex/silex/tests/Silex/Tests/Route/SecurityRoute.php new file mode 100644 index 00000000..48237194 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Route/SecurityRoute.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Route; + +use Silex\Route; + +class SecurityRoute extends Route +{ + use Route\SecurityTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Route/SecurityTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Route/SecurityTraitTest.php new file mode 100644 index 00000000..352a77bd --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Route/SecurityTraitTest.php @@ -0,0 +1,85 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Route; + +use Silex\Application; +use Silex\Provider\SecurityServiceProvider; +use Symfony\Component\HttpFoundation\Request; + +/** + * SecurityTrait test cases. + * + * @author Fabien Potencier + */ +class SecurityTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testSecureWithNoAuthenticatedUser() + { + $app = $this->createApplication(); + + $app->get('/', function () { return 'foo'; }) + ->secure('ROLE_ADMIN') + ; + + $request = Request::create('/'); + $response = $app->handle($request); + $this->assertEquals(401, $response->getStatusCode()); + } + + public function testSecureWithAuthorizedRoles() + { + $app = $this->createApplication(); + + $app->get('/', function () { return 'foo'; }) + ->secure('ROLE_ADMIN') + ; + + $request = Request::create('/'); + $request->headers->set('PHP_AUTH_USER', 'fabien'); + $request->headers->set('PHP_AUTH_PW', 'foo'); + $response = $app->handle($request); + $this->assertEquals(200, $response->getStatusCode()); + } + + public function testSecureWithUnauthorizedRoles() + { + $app = $this->createApplication(); + + $app->get('/', function () { return 'foo'; }) + ->secure('ROLE_SUPER_ADMIN') + ; + + $request = Request::create('/'); + $request->headers->set('PHP_AUTH_USER', 'fabien'); + $request->headers->set('PHP_AUTH_PW', 'foo'); + $response = $app->handle($request); + $this->assertEquals(403, $response->getStatusCode()); + } + + private function createApplication() + { + $app = new Application(); + $app['route_class'] = 'Silex\Tests\Route\SecurityRoute'; + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + 'users' => array( + 'fabien' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + ), + ), + ), + )); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/RouterTest.php b/vendor/silex/silex/tests/Silex/Tests/RouterTest.php new file mode 100644 index 00000000..665891ea --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/RouterTest.php @@ -0,0 +1,285 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; + +/** + * Router test cases. + * + * @author Igor Wiedler + */ +class RouterTest extends \PHPUnit_Framework_TestCase +{ + public function testMapRouting() + { + $app = new Application(); + + $app->match('/foo', function () { + return 'foo'; + }); + + $app->match('/bar', function () { + return 'bar'; + }); + + $app->match('/', function () { + return 'root'; + }); + + $this->checkRouteResponse($app, '/foo', 'foo'); + $this->checkRouteResponse($app, '/bar', 'bar'); + $this->checkRouteResponse($app, '/', 'root'); + } + + public function testStatusCode() + { + $app = new Application(); + + $app->put('/created', function () { + return new Response('', 201); + }); + + $app->match('/forbidden', function () { + return new Response('', 403); + }); + + $app->match('/not_found', function () { + return new Response('', 404); + }); + + $request = Request::create('/created', 'put'); + $response = $app->handle($request); + $this->assertEquals(201, $response->getStatusCode()); + + $request = Request::create('/forbidden'); + $response = $app->handle($request); + $this->assertEquals(403, $response->getStatusCode()); + + $request = Request::create('/not_found'); + $response = $app->handle($request); + $this->assertEquals(404, $response->getStatusCode()); + } + + public function testRedirect() + { + $app = new Application(); + + $app->match('/redirect', function () { + return new RedirectResponse('/target'); + }); + + $app->match('/redirect2', function () use ($app) { + return $app->redirect('/target2'); + }); + + $request = Request::create('/redirect'); + $response = $app->handle($request); + $this->assertTrue($response->isRedirect('/target')); + + $request = Request::create('/redirect2'); + $response = $app->handle($request); + $this->assertTrue($response->isRedirect('/target2')); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testMissingRoute() + { + $app = new Application(); + unset($app['exception_handler']); + + $request = Request::create('/baz'); + $app->handle($request); + } + + public function testMethodRouting() + { + $app = new Application(); + + $app->match('/foo', function () { + return 'foo'; + }); + + $app->match('/bar', function () { + return 'bar'; + })->method('GET|POST'); + + $app->get('/resource', function () { + return 'get resource'; + }); + + $app->post('/resource', function () { + return 'post resource'; + }); + + $app->put('/resource', function () { + return 'put resource'; + }); + + $app->patch('/resource', function () { + return 'patch resource'; + }); + + $app->delete('/resource', function () { + return 'delete resource'; + }); + + $this->checkRouteResponse($app, '/foo', 'foo'); + $this->checkRouteResponse($app, '/bar', 'bar'); + $this->checkRouteResponse($app, '/bar', 'bar', 'post'); + $this->checkRouteResponse($app, '/resource', 'get resource'); + $this->checkRouteResponse($app, '/resource', 'post resource', 'post'); + $this->checkRouteResponse($app, '/resource', 'put resource', 'put'); + $this->checkRouteResponse($app, '/resource', 'patch resource', 'patch'); + $this->checkRouteResponse($app, '/resource', 'delete resource', 'delete'); + } + + public function testRequestShouldBeStoredRegardlessOfRouting() + { + $app = new Application(); + + $app->get('/foo', function (Request $request) use ($app) { + return new Response($request->getRequestUri()); + }); + + $app->error(function ($e, Request $request, $code) use ($app) { + return new Response($request->getRequestUri()); + }); + + foreach (array('/foo', '/bar') as $path) { + $request = Request::create($path); + $response = $app->handle($request); + $this->assertContains($path, $response->getContent()); + } + } + + public function testTrailingSlashBehavior() + { + $app = new Application(); + + $app->get('/foo/', function () use ($app) { + return new Response('ok'); + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + + $this->assertEquals(301, $response->getStatusCode()); + $this->assertEquals('/foo/', $response->getTargetUrl()); + } + + public function testHostSpecification() + { + $route = new \Silex\Route(); + + $this->assertSame($route, $route->host('{locale}.example.com')); + $this->assertEquals('{locale}.example.com', $route->getHost()); + } + + public function testRequireHttpRedirect() + { + $app = new Application(); + + $app->match('/secured', function () { + return 'secured content'; + }) + ->requireHttp(); + + $request = Request::create('https://example.com/secured'); + $response = $app->handle($request); + $this->assertTrue($response->isRedirect('http://example.com/secured')); + } + + public function testRequireHttpsRedirect() + { + $app = new Application(); + + $app->match('/secured', function () { + return 'secured content'; + }) + ->requireHttps(); + + $request = Request::create('http://example.com/secured'); + $response = $app->handle($request); + $this->assertTrue($response->isRedirect('https://example.com/secured')); + } + + public function testRequireHttpsRedirectIncludesQueryString() + { + $app = new Application(); + + $app->match('/secured', function () { + return 'secured content'; + }) + ->requireHttps(); + + $request = Request::create('http://example.com/secured?query=string'); + $response = $app->handle($request); + $this->assertTrue($response->isRedirect('https://example.com/secured?query=string')); + } + + public function testConditionOnRoute() + { + $app = new Application(); + $app->match('/secured', function () { + return 'secured content'; + }) + ->when('request.isSecure() == true'); + + $request = Request::create('http://example.com/secured'); + $response = $app->handle($request); + $this->assertEquals(404, $response->getStatusCode()); + } + + public function testClassNameControllerSyntax() + { + $app = new Application(); + + $app->get('/foo', 'Silex\Tests\MyController::getFoo'); + + $this->checkRouteResponse($app, '/foo', 'foo'); + } + + public function testClassNameControllerSyntaxWithStaticMethod() + { + $app = new Application(); + + $app->get('/bar', 'Silex\Tests\MyController::getBar'); + + $this->checkRouteResponse($app, '/bar', 'bar'); + } + + protected function checkRouteResponse(Application $app, $path, $expectedContent, $method = 'get', $message = null) + { + $request = Request::create($path, $method); + $response = $app->handle($request); + $this->assertEquals($expectedContent, $response->getContent(), $message); + } +} + +class MyController +{ + public function getFoo() + { + return 'foo'; + } + + public static function getBar() + { + return 'bar'; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverRouterTest.php b/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverRouterTest.php new file mode 100644 index 00000000..4bc88a45 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverRouterTest.php @@ -0,0 +1,43 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Silex\Provider\ServiceControllerServiceProvider; +use Symfony\Component\HttpFoundation\Request; + +/** + * Router test cases, using the ServiceControllerResolver. + */ +class ServiceControllerResolverRouterTest extends RouterTest +{ + public function testServiceNameControllerSyntax() + { + $app = new Application(); + $app->register(new ServiceControllerServiceProvider()); + + $app['service_name'] = function () { + return new MyController(); + }; + + $app->get('/bar', 'service_name:getBar'); + + $this->checkRouteResponse($app, '/bar', 'bar'); + } + + protected function checkRouteResponse(Application $app, $path, $expectedContent, $method = 'get', $message = null) + { + $request = Request::create($path, $method); + $response = $app->handle($request); + $this->assertEquals($expectedContent, $response->getContent(), $message); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverTest.php b/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverTest.php new file mode 100644 index 00000000..f732c7de --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverTest.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 Silex\Tests; + +use Silex\ServiceControllerResolver; +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; + +/** + * Unit tests for ServiceControllerResolver, see ServiceControllerResolverRouterTest for some + * integration tests. + */ +class ServiceControllerResolverTest extends \PHPUnit_Framework_Testcase +{ + private $app; + private $mockCallbackResolver; + private $mockResolver; + private $resolver; + + public function setup() + { + $this->mockResolver = $this->getMockBuilder('Symfony\Component\HttpKernel\Controller\ControllerResolverInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->mockCallbackResolver = $this->getMockBuilder('Silex\CallbackResolver') + ->disableOriginalConstructor() + ->getMock(); + + $this->app = new Application(); + $this->resolver = new ServiceControllerResolver($this->mockResolver, $this->mockCallbackResolver); + } + + public function testShouldResolveServiceController() + { + $this->mockCallbackResolver->expects($this->once()) + ->method('isValid') + ->will($this->returnValue(true)); + + $this->mockCallbackResolver->expects($this->once()) + ->method('convertCallback') + ->with('some_service:methodName') + ->will($this->returnValue(array('callback'))); + + $this->app['some_service'] = function () { return new \stdClass(); }; + + $req = Request::create('/'); + $req->attributes->set('_controller', 'some_service:methodName'); + + $this->assertEquals(array('callback'), $this->resolver->getController($req)); + } + + public function testShouldUnresolvedControllerNames() + { + $req = Request::create('/'); + $req->attributes->set('_controller', 'some_class::methodName'); + + $this->mockCallbackResolver->expects($this->once()) + ->method('isValid') + ->with('some_class::methodName') + ->will($this->returnValue(false)); + + $this->mockResolver->expects($this->once()) + ->method('getController') + ->with($req) + ->will($this->returnValue(123)); + + $this->assertEquals(123, $this->resolver->getController($req)); + } + + public function testShouldDelegateGetArguments() + { + $req = Request::create('/'); + $this->mockResolver->expects($this->once()) + ->method('getArguments') + ->with($req) + ->will($this->returnValue(123)); + + $this->assertEquals(123, $this->resolver->getArguments($req, function () {})); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/StreamTest.php b/vendor/silex/silex/tests/Silex/Tests/StreamTest.php new file mode 100644 index 00000000..601f0e60 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/StreamTest.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; + +/** + * Stream test cases. + * + * @author Igor Wiedler + */ +class StreamTest extends \PHPUnit_Framework_TestCase +{ + public function testStreamReturnsStreamingResponse() + { + $app = new Application(); + + $response = $app->stream(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response); + $this->assertSame(false, $response->getContent()); + } + + public function testStreamActuallyStreams() + { + $i = 0; + + $stream = function () use (&$i) { + ++$i; + }; + + $app = new Application(); + $response = $app->stream($stream); + + $this->assertEquals(0, $i); + + $request = Request::create('/stream'); + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(1, $i); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/WebTestCaseTest.php b/vendor/silex/silex/tests/Silex/Tests/WebTestCaseTest.php new file mode 100644 index 00000000..474ffc38 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/WebTestCaseTest.php @@ -0,0 +1,78 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Silex\WebTestCase; +use Symfony\Component\HttpFoundation\Request; + +/** + * Functional test cases. + * + * @author Igor Wiedler + */ +class WebTestCaseTest extends WebTestCase +{ + public function createApplication() + { + $app = new Application(); + + $app->match('/hello', function () { + return 'world'; + }); + + $app->match('/html', function () { + return '

title

'; + }); + + $app->match('/server', function (Request $request) use ($app) { + $user = $request->server->get('PHP_AUTH_USER'); + $pass = $request->server->get('PHP_AUTH_PW'); + + return "

$user:$pass

"; + }); + + return $app; + } + + public function testGetHello() + { + $client = $this->createClient(); + + $client->request('GET', '/hello'); + $response = $client->getResponse(); + $this->assertTrue($response->isSuccessful()); + $this->assertEquals('world', $response->getContent()); + } + + public function testCrawlerFilter() + { + $client = $this->createClient(); + + $crawler = $client->request('GET', '/html'); + $this->assertEquals('title', $crawler->filter('h1')->text()); + } + + public function testServerVariables() + { + $user = 'klaus'; + $pass = '123456'; + + $client = $this->createClient(array( + 'PHP_AUTH_USER' => $user, + 'PHP_AUTH_PW' => $pass, + )); + + $crawler = $client->request('GET', '/server'); + $this->assertEquals("$user:$pass", $crawler->filter('h1')->text()); + } +} diff --git a/vendor/silex/web-profiler/.editorconfig b/vendor/silex/web-profiler/.editorconfig new file mode 100644 index 00000000..153cf3ef --- /dev/null +++ b/vendor/silex/web-profiler/.editorconfig @@ -0,0 +1,10 @@ +; top-most EditorConfig file +root = true + +; Unix-style newlines +[*] +end_of_line = LF + +[*.php] +indent_style = space +indent_size = 4 diff --git a/vendor/silex/web-profiler/.gitignore b/vendor/silex/web-profiler/.gitignore new file mode 100644 index 00000000..ea682051 --- /dev/null +++ b/vendor/silex/web-profiler/.gitignore @@ -0,0 +1,4 @@ +/vendor/ +composer.lock +phpunit.xml +/Tests/cache diff --git a/vendor/silex/web-profiler/.travis.yml b/vendor/silex/web-profiler/.travis.yml new file mode 100644 index 00000000..fa9bcd6f --- /dev/null +++ b/vendor/silex/web-profiler/.travis.yml @@ -0,0 +1,25 @@ +language: php + +sudo: false + +cache: + directories: + - $HOME/.composer/cache/files + - .phpunit + +matrix: + include: + - php: 5.5 + - php: 5.6 + - php: 7.0 + - php: 7.1 + env: SYMFONY_DEPS_VERSION=3 + - php: hhvm + +before_script: + - sh -c "if [ '$SYMFONY_DEPS_VERSION' = '3.0' ]; then sed -i 's/~2\.8|3\.0\.\*/3.0.*@dev/g' composer.json; composer update; fi" + - sh -c "if [ '$SYMFONY_DEPS_VERSION' = '' ]; then sed -i 's/~2\.8|3\.0\.\*/2.8.*@dev/g' composer.json; composer update; fi" + - travis_retry composer install + +script: + - ./vendor/bin/simple-phpunit diff --git a/vendor/silex/web-profiler/LICENSE b/vendor/silex/web-profiler/LICENSE new file mode 100644 index 00000000..ac30964e --- /dev/null +++ b/vendor/silex/web-profiler/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012-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/vendor/silex/web-profiler/README.rst b/vendor/silex/web-profiler/README.rst new file mode 100644 index 00000000..c1f64656 --- /dev/null +++ b/vendor/silex/web-profiler/README.rst @@ -0,0 +1,52 @@ +Silex Web Profiler +================== + +The Silex Web Profiler service provider allows you to use the wonderful Symfony +web debug toolbar and the Symfony profiler in your Silex 2.x application. + +.. note:: + + If you are using the 1.x Silex version, read the `specific documentation + `_. + +To install this library, run the command below and you will get the latest +version: + +.. code-block:: bash + + composer require 'silex/web-profiler:^2.0' + +And enable it in your application: + +.. code-block:: php + + use Silex\Provider; + + $app->register(new Provider\WebProfilerServiceProvider(), array( + 'profiler.cache_dir' => __DIR__.'/../cache/profiler', + 'profiler.mount_prefix' => '/_profiler', // this is the default + )); + +The provider depends on ``ServiceControllerServiceProvider``, +``TwigServiceProvider``, and ``HttpFragmentServiceProvider`` so you also need +to enable those if that's not already the case: + +.. code-block:: php + + $app->register(new Provider\HttpFragmentServiceProvider()); + $app->register(new Provider\ServiceControllerServiceProvider()); + $app->register(new Provider\TwigServiceProvider()); + +If you are using ``FormServiceProvider``, the ``WebProfilerServiceProvider`` +will detect that and enable the corresponding panels. + +*Make sure to register all other required or used service providers before* +``WebProfilerServiceProvider``. + +If you are using ``MonologServiceProvider`` for logs, you must also add +``symfony/monolog-bridge`` as a Composer dependency to get the +logs in the profiler. + +If you are using ``VarDumperServiceProvider``, add ``symfony/debug-bundle`` as +a Composer dependency to display VarDumper dumps in the toolbar and the +profiler. diff --git a/vendor/silex/web-profiler/Tests/WebProfilerTest.php b/vendor/silex/web-profiler/Tests/WebProfilerTest.php new file mode 100644 index 00000000..aafe0498 --- /dev/null +++ b/vendor/silex/web-profiler/Tests/WebProfilerTest.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Tests; + +use Silex\Application; +use Silex\WebTestCase; +use Silex\Provider; +use Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder; + +class WebProfilerTest extends WebTestCase +{ + public function createApplication() + { + $app = new Application(); + + // Service providers + $app->register(new Provider\HttpFragmentServiceProvider()); + $app->register(new Provider\ServiceControllerServiceProvider()); + $app->register(new Provider\SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'secured' => array( + 'pattern' => '^/secured', + 'form' => array('login_path' => '/login', 'check_path' => '/login_check'), + 'logout' => array('logout_path' => '/secured/logout', 'invalidate_session' => true), + 'users' => array( + 'admin' => array('ROLE_ADMIN', 'admin_pass'), + ), + ), + 'default' => array( + 'pattern' => '^.*$', + 'anonymous' => true, + ), + ), + 'security.encoder.digest' => new PlaintextPasswordEncoder(), + 'security.role_hierarchy' => array( + 'ROLE_ADMIN' => array('ROLE_USER', 'ROLE_ALLOWED_TO_SWITCH'), + ), + )); + $app->register(new Provider\TwigServiceProvider(), array( + 'twig.templates' => array( + 'index.twig' => 'OK', + ), + )); + + $app->register(new Provider\WebProfilerServiceProvider(), array( + 'profiler.cache_dir' => __DIR__.'/cache/profiler', + )); + + // Test Config + $app['debug'] = true; + unset($app['exception_handler']); + $app['session.test'] = true; + + // Test route + $app->get('/', function () use ($app) { + return $app['twig']->render('index.twig'); + }); + + return $app; + } + + public function testRun() + { + $client = $this->createClient(); + $crawler = $client->request('GET', '/'); + + $this->assertTrue($client->getResponse()->isOk(), 'Response successful'); + $this->assertCount(1, $crawler->filter('.sf-toolbar'), 'WDT is inserted.'); + $this->assertTrue($client->getResponse()->headers->has('X-Debug-Token'), 'Profile ID'); + $this->assertTrue($client->getResponse()->headers->has('X-Debug-Token-Link'), 'Profiler link'); + + $link = $client->getResponse()->headers->get('X-Debug-Token-Link'); + + $crawler = $client->request('GET', $link); + $this->assertTrue($client->getResponse()->isOk(), 'Profile accessible'); + + $client->followRedirects(true); + $crawler = $client->request('GET', '/_profiler/'); + $this->assertTrue($client->getResponse()->isOk(), 'Profiler accessible'); + } + + public function testRoutingProfiler() + { + $client = $this->createClient(); + $client->request('GET', '/'); + + $link = $client->getResponse()->headers->get('X-Debug-Token-Link'); + $crawler = $client->request('GET', $link); + + $crawler = $client->click($crawler->selectLink('Routing')->link()); + $this->assertTrue($client->getResponse()->isOk(), 'Routing profiler is enabled'); + $this->assertCount(1, $crawler->filter('h2:contains("Routing for"), h3:contains("Route Matching Logs")'), 'Routing profiler is working'); + } + + public function testTwigProfiler() + { + $client = $this->createClient(); + $client->request('GET', '/'); + + $link = $client->getResponse()->headers->get('X-Debug-Token-Link'); + $crawler = $client->request('GET', $link); + + $crawler = $client->click($crawler->selectLink('Twig')->link()); + $this->assertTrue($client->getResponse()->isOk(), 'Twig profiler is enabled'); + $this->assertCount(1, $crawler->filter('h2:contains("Twig Stats"), h2:contains("Twig Metrics")'), 'Twig profiler is working'); + } + + public function testSecurityProfiler() + { + $client = $this->createClient(); + $client->request('GET', '/'); + + $link = $client->getResponse()->headers->get('X-Debug-Token-Link'); + $crawler = $client->request('GET', $link); + + $crawler = $client->click($crawler->selectLink('Security')->link()); + $this->assertTrue($client->getResponse()->isOk(), 'Security profiler is enabled'); + $this->assertCount(1, $crawler->filter('h2:contains("Security Token")'), 'Security profiler is working'); + $this->assertCount(1, $crawler->filter('span:contains("Anonymous")'), 'Profiler gets anonymous token'); + } +} diff --git a/vendor/silex/web-profiler/WebProfilerServiceProvider.php b/vendor/silex/web-profiler/WebProfilerServiceProvider.php new file mode 100644 index 00000000..49073679 --- /dev/null +++ b/vendor/silex/web-profiler/WebProfilerServiceProvider.php @@ -0,0 +1,402 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\ControllerProviderInterface; +use Silex\Api\BootableProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Silex\Application; +use Silex\ServiceControllerResolver; +use Symfony\Bridge\Twig\DataCollector\TwigDataCollector; +use Symfony\Bridge\Twig\Extension\CodeExtension; +use Symfony\Bridge\Twig\Extension\ProfilerExtension; +use Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector; +use Symfony\Bundle\WebProfilerBundle\Controller\ExceptionController; +use Symfony\Bundle\WebProfilerBundle\Controller\RouterController; +use Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController; +use Symfony\Bundle\WebProfilerBundle\EventListener\WebDebugToolbarListener; +use Symfony\Bundle\WebProfilerBundle\Twig\WebProfilerExtension; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Form\Extension\DataCollector\FormDataCollector; +use Symfony\Component\Form\Extension\DataCollector\FormDataExtractor; +use Symfony\Component\Form\Extension\DataCollector\Proxy\ResolvedTypeFactoryDataCollectorProxy; +use Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\Profiler\Profiler; +use Symfony\Component\HttpKernel\EventListener\DumpListener; +use Symfony\Component\HttpKernel\EventListener\ProfilerListener; +use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage; +use Symfony\Component\HttpKernel\DataCollector\AjaxDataCollector; +use Symfony\Component\HttpKernel\DataCollector\ConfigDataCollector; +use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; +use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector; +use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector; +use Symfony\Component\HttpKernel\DataCollector\RouterDataCollector; +use Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector; +use Symfony\Component\HttpKernel\DataCollector\TimeDataCollector; +use Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector; +use Symfony\Component\HttpKernel\DataCollector\EventDataCollector; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher; +use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; +use Symfony\Component\Security\Core\Role\RoleHierarchy; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Translation\DataCollector\TranslationDataCollector; +use Symfony\Component\Translation\DataCollectorTranslator; +use Symfony\Component\Yaml\Yaml; + +/** + * Symfony Web Profiler provider. + * + * @author Fabien Potencier + */ +class WebProfilerServiceProvider implements ServiceProviderInterface, ControllerProviderInterface, BootableProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['profiler.mount_prefix'] = '/_profiler'; + $app->extend('dispatcher', function ($dispatcher, $app) { + return new TraceableEventDispatcher($dispatcher, $app['stopwatch'], $app['logger']); + }); + + $baseDir = $this->getBaseDir(); + + $app['data_collector.templates'] = function ($app) { + $templates = array( + array('config', '@WebProfiler/Collector/config.html.twig'), + array('request', '@WebProfiler/Collector/request.html.twig'), + array('exception', '@WebProfiler/Collector/exception.html.twig'), + array('events', '@WebProfiler/Collector/events.html.twig'), + array('logger', '@WebProfiler/Collector/logger.html.twig'), + array('time', '@WebProfiler/Collector/time.html.twig'), + array('router', '@WebProfiler/Collector/router.html.twig'), + array('memory', '@WebProfiler/Collector/memory.html.twig'), + array('form', '@WebProfiler/Collector/form.html.twig'), + array('translation', '@WebProfiler/Collector/translation.html.twig'), + ); + + if (class_exists('Symfony\Bridge\Twig\Extension\ProfilerExtension')) { + $templates[] = array('twig', '@WebProfiler/Collector/twig.html.twig'); + } + + if (isset($app['var_dumper.cli_dumper']) && $app['profiler.templates_path.debug']) { + $templates[] = array('dump', '@Debug/Profiler/dump.html.twig'); + } + + if (class_exists('Symfony\Component\HttpKernel\DataCollector\AjaxDataCollector')) { + $templates[] = array('ajax', '@WebProfiler/Collector/ajax.html.twig'); + } + + return $templates; + }; + + $app['data_collectors'] = function ($app) { + return array( + 'config' => function ($app) { return new ConfigDataCollector('Silex', Application::VERSION); }, + 'request' => function ($app) { return new RequestDataCollector(); }, + 'exception' => function ($app) { return new ExceptionDataCollector(); }, + 'events' => function ($app) { return new EventDataCollector($app['dispatcher']); }, + 'logger' => function ($app) { return new LoggerDataCollector($app['logger']); }, + 'time' => function ($app) { return new TimeDataCollector(null, $app['stopwatch']); }, + 'router' => function ($app) { return new RouterDataCollector(); }, + 'memory' => function ($app) { return new MemoryDataCollector(); }, + ); + }; + + if (isset($app['form.resolved_type_factory']) && class_exists('Symfony\Component\Form\Extension\DataCollector\FormDataCollector')) { + $app['data_collectors.form.extractor'] = function () { return new FormDataExtractor(); }; + $app['data_collectors.form.collector'] = function ($app) { return new FormDataCollector($app['data_collectors.form.extractor']); }; + + $app->extend('data_collectors', function ($collectors, $app) { + $collectors['form'] = function ($app) { return $app['data_collectors.form.collector']; }; + + return $collectors; + }); + + $app->extend('form.resolved_type_factory', function ($factory, $app) { + return new ResolvedTypeFactoryDataCollectorProxy($factory, $app['data_collectors']['form']($app)); + }); + + $app->extend('form.type.extensions', function ($extensions, $app) { + $extensions[] = new DataCollectorTypeExtension($app['data_collectors']['form']($app)); + + return $extensions; + }); + } + + if (class_exists('Symfony\Bridge\Twig\Extension\ProfilerExtension')) { + $app['data_collectors'] = $app->extend('data_collectors', function ($collectors, $app) { + $collectors['twig'] = function ($app) { + return new TwigDataCollector($app['twig.profiler.profile']); + }; + + return $collectors; + }); + + $app['twig.profiler.profile'] = function () { + return new \Twig_Profiler_Profile(); + }; + } + + if (isset($app['var_dumper.cli_dumper'])) { + $app['var_dumper.dump_listener'] = function ($app) { + return new DumpListener($app['var_dumper.cloner'], $app['var_dumper.data_collector']); + }; + + $app['data_collectors'] = $app->extend('data_collectors', function ($collectors, $app) { + if ($app['profiler.templates_path.debug']) { + $collectors['dump'] = function ($app) { + $dumper = null === $app['var_dumper.dump_destination'] ? null : $app['var_dumper.cli_dumper']; + + return $app['var_dumper.data_collector'] = new DumpDataCollector($app['stopwatch'], null, $app['charset'], $app['request_stack'], $dumper); + }; + } + + return $collectors; + }); + } + + if (class_exists('Symfony\Component\HttpKernel\DataCollector\AjaxDataCollector')) { + $app['data_collectors'] = $app->extend('data_collectors', function ($collectors, $app) { + $collectors['ajax'] = function ($app) { + return new AjaxDataCollector(); + }; + + return $collectors; + }); + } + + if (isset($app['security.token_storage']) && class_exists('Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector')) { + $app->extend('data_collectors', function ($collectors, $app) { + $collectors['security'] = function ($app) { + $roleHierarchy = new RoleHierarchy($app['security.role_hierarchy']); + $logoutUrlGenerator = new LogoutUrlGenerator($app['request_stack'], $app['url_generator'], $app['security.token_storage']); + + return new SecurityDataCollector($app['security.token_storage'], $roleHierarchy, $logoutUrlGenerator); + }; + + return $collectors; + }); + + $app->extend('data_collector.templates', function ($templates) { + $templates[] = array('security', '@Security/Collector/security.html.twig'); + + return $templates; + }); + + $app->extend('twig.loader.filesystem', function ($loader, $app) { + if ($app['profiler.templates_path.security']) { + $loader->addPath($app['profiler.templates_path.security'], 'Security'); + } + + return $loader; + }); + + $app['profiler.templates_path.security'] = function () { + $r = new \ReflectionClass('Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector'); + + return dirname(dirname($r->getFileName())).'/Resources/views'; + }; + + $app['twig'] = $app->extend('twig', function ($twig, $app) { + $twig->addFilter(new \Twig_SimpleFilter('yaml_encode', function (array $var) { + return Yaml::dump($var); + })); + + $twig->addFunction(new \Twig_SimpleFunction('yaml_encode', function (array $var) { + return Yaml::dump($var); + })); + + return $twig; + }); + } + + if (isset($app['translator']) && class_exists('Symfony\Component\Translation\DataCollector\TranslationDataCollector')) { + $app['data_collectors'] = $app->extend('data_collectors', function ($collectors, $app) { + $collectors['translation'] = function ($app) { + return new TranslationDataCollector($app['translator']); + }; + + return $collectors; + }); + + $app->extend('translator', function ($translator, $app) { + return new DataCollectorTranslator($translator); + }); + } + + $app['web_profiler.controller.profiler'] = function ($app) use ($baseDir) { + return new ProfilerController($app['url_generator'], $app['profiler'], $app['twig'], $app['data_collector.templates'], $app['web_profiler.debug_toolbar.position'], null, $baseDir); + }; + + $app['web_profiler.controller.router'] = function ($app) { + return new RouterController($app['profiler'], $app['twig'], isset($app['request_matcher']) ? $app['request_matcher'] : null, $app['routes']); + }; + + $app['web_profiler.controller.exception'] = function ($app) { + return new ExceptionController($app['profiler'], $app['twig'], $app['debug']); + }; + + $app['web_profiler.toolbar.listener'] = function ($app) { + $mode = $app['web_profiler.debug_toolbar.enable'] ? WebDebugToolbarListener::ENABLED : WebDebugToolbarListener::DISABLED; + + return new WebDebugToolbarListener($app['twig'], $app['web_profiler.debug_toolbar.intercept_redirects'], $mode, $app['web_profiler.debug_toolbar.position'], $app['url_generator']); + }; + + $app['profiler'] = function ($app) { + $profiler = new Profiler($app['profiler.storage'], $app['logger']); + + foreach ($app['data_collectors'] as $collector) { + $profiler->add($collector($app)); + } + + return $profiler; + }; + + $app['profiler.storage'] = function ($app) { + return new FileProfilerStorage('file:'.$app['profiler.cache_dir']); + }; + + $app['profiler.request_matcher'] = null; + $app['profiler.only_exceptions'] = false; + $app['profiler.only_master_requests'] = false; + $app['web_profiler.debug_toolbar.enable'] = true; + $app['web_profiler.debug_toolbar.position'] = 'bottom'; + $app['web_profiler.debug_toolbar.intercept_redirects'] = false; + + $app['profiler.listener'] = function ($app) { + if (Kernel::VERSION_ID >= 20800) { + return new ProfilerListener($app['profiler'], $app['request_stack'], $app['profiler.request_matcher'], $app['profiler.only_exceptions'], $app['profiler.only_master_requests']); + } else { + return new ProfilerListener($app['profiler'], $app['profiler.request_matcher'], $app['profiler.only_exceptions'], $app['profiler.only_master_requests'], $app['request_stack']); + } + }; + + $app['stopwatch'] = function () { + return new Stopwatch(); + }; + + $app['code.file_link_format'] = null; + + $app->extend('twig', function ($twig, $app) use ($baseDir) { + if (class_exists('Symfony\Component\HttpKernel\Debug\FileLinkFormatter')) { + $app['code.file_link_format'] = new FileLinkFormatter($app['code.file_link_format'], $app['request_stack'], $baseDir, '/_profiler/open?file=%f&line=%l#line%l'); + } + + $twig->addExtension(new CodeExtension($app['code.file_link_format'], '', $app['charset'])); + + if (class_exists('Symfony\Bundle\WebProfilerBundle\Twig\WebProfilerExtension')) { + $twig->addExtension(new WebProfilerExtension()); + } + + if (class_exists('Symfony\Bridge\Twig\Extension\ProfilerExtension')) { + $twig->addExtension(new ProfilerExtension($app['twig.profiler.profile'], $app['stopwatch'])); + } + + return $twig; + }); + + $app->extend('twig.loader.filesystem', function ($loader, $app) { + $loader->addPath($app['profiler.templates_path'], 'WebProfiler'); + if ($app['profiler.templates_path.debug']) { + $loader->addPath($app['profiler.templates_path.debug'], 'Debug'); + } + + return $loader; + }); + + $app['profiler.templates_path'] = function () { + $r = new \ReflectionClass('Symfony\Bundle\WebProfilerBundle\EventListener\WebDebugToolbarListener'); + + return dirname(dirname($r->getFileName())).'/Resources/views'; + }; + + $app['profiler.templates_path.debug'] = function () { + // This code cannot be simplified as all classes in the bundle depend + // on packages that are not required by Silex + foreach (spl_autoload_functions() as $autoloader) { + if (!is_array($autoloader) || !method_exists($autoloader[0], 'findFile')) { + continue; + } + + if ($file = $autoloader[0]->findFile('Symfony\Bundle\DebugBundle\DebugBundle')) { + return dirname($file).'/Resources/views'; + } + } + }; + } + + public function connect(Application $app) + { + if (!$app['resolver'] instanceof ServiceControllerResolver) { + // using RuntimeException crashes PHP?! + throw new \LogicException('You must enable the ServiceController service provider to be able to use the WebProfiler.'); + } + + $controllers = $app['controllers_factory']; + + $controllers->get('/router/{token}', 'web_profiler.controller.router:panelAction')->bind('_profiler_router'); + $controllers->get('/exception/{token}.css', 'web_profiler.controller.exception:cssAction')->bind('_profiler_exception_css'); + $controllers->get('/exception/{token}', 'web_profiler.controller.exception:showAction')->bind('_profiler_exception'); + $controllers->get('/search', 'web_profiler.controller.profiler:searchAction')->bind('_profiler_search'); + $controllers->get('/search_bar', 'web_profiler.controller.profiler:searchBarAction')->bind('_profiler_search_bar'); + $controllers->get('/purge', 'web_profiler.controller.profiler:purgeAction')->bind('_profiler_purge'); + $controllers->get('/info/{about}', 'web_profiler.controller.profiler:infoAction')->bind('_profiler_info'); + $controllers->get('/phpinfo', 'web_profiler.controller.profiler:phpinfoAction')->bind('_profiler_phpinfo'); + $controllers->get('/open', 'web_profiler.controller.profiler:openAction')->bind('_profiler_open_file'); + $controllers->get('/{token}/search/results', 'web_profiler.controller.profiler:searchResultsAction')->bind('_profiler_search_results'); + $controllers->get('/{token}', 'web_profiler.controller.profiler:panelAction')->bind('_profiler'); + $controllers->get('/wdt/{token}', 'web_profiler.controller.profiler:toolbarAction')->bind('_wdt'); + $controllers->get('/', 'web_profiler.controller.profiler:homeAction')->bind('_profiler_home'); + + return $controllers; + } + + public function boot(Application $app) + { + $app->mount($app['profiler.mount_prefix'], $this->connect($app)); + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['profiler.listener']); + + if ($app['web_profiler.debug_toolbar.enable']) { + $dispatcher->addSubscriber($app['web_profiler.toolbar.listener']); + } + + $dispatcher->addSubscriber($app['profiler']->get('request')); + + if (isset($app['var_dumper.data_collector'])) { + $dispatcher->addSubscriber($app['var_dumper.dump_listener']); + } + } + + private function getBaseDir() + { + $baseDir = array(); + $rootDir = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + $rootDir = end($rootDir)['file']; + $rootDir = explode(DIRECTORY_SEPARATOR, realpath($rootDir) ?: $rootDir); + $providerDir = explode(DIRECTORY_SEPARATOR, __DIR__); + for ($i = 0; isset($rootDir[$i], $providerDir[$i]); ++$i) { + if ($rootDir[$i] !== $providerDir[$i]) { + break; + } + $baseDir[] = $rootDir[$i]; + } + + return implode(DIRECTORY_SEPARATOR, $baseDir); + } +} diff --git a/vendor/silex/web-profiler/composer.json b/vendor/silex/web-profiler/composer.json new file mode 100644 index 00000000..05d385e0 --- /dev/null +++ b/vendor/silex/web-profiler/composer.json @@ -0,0 +1,38 @@ +{ + "name": "silex/web-profiler", + "description": "A WebProfiler for Silex", + "homepage": "http://silex.sensiolabs.org/", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "require": { + "silex/silex": "^2.0", + "symfony/web-profiler-bundle": "^2.8|^3.0", + "symfony/twig-bridge": "^2.8|^3.0", + "symfony/stopwatch": "^2.8|^3.0" + }, + "conflict": { + "symfony/web-profiler-bundle": "3.1.0" + }, + "require-dev": { + "symfony/browser-kit": "^2.8|^3.0", + "symfony/css-selector": "^2.8|^3.0", + "symfony/debug-bundle": "^2.8|^3.0", + "symfony/phpunit-bridge": "~3.2", + "symfony/security": "^2.8|^3.0", + "symfony/security-bundle": "^2.8|^3.0", + "symfony/translation": "^2.8|^3.0" + }, + "autoload": { + "psr-4": { "Silex\\Provider\\": "" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/vendor/silex/web-profiler/phpunit.xml.dist b/vendor/silex/web-profiler/phpunit.xml.dist new file mode 100644 index 00000000..4d18f351 --- /dev/null +++ b/vendor/silex/web-profiler/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + + + + diff --git a/vendor/symfony/asset/.gitignore b/vendor/symfony/asset/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/asset/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/asset/CHANGELOG.md b/vendor/symfony/asset/CHANGELOG.md new file mode 100644 index 00000000..72030a9d --- /dev/null +++ b/vendor/symfony/asset/CHANGELOG.md @@ -0,0 +1,12 @@ +CHANGELOG +========= + +3.3.0 +----- + * Added `JsonManifestVersionStrategy` as a way to read final, + versioned paths from a JSON manifest file. + +2.7.0 +----- + + * added the component diff --git a/vendor/symfony/asset/Context/ContextInterface.php b/vendor/symfony/asset/Context/ContextInterface.php new file mode 100644 index 00000000..83282021 --- /dev/null +++ b/vendor/symfony/asset/Context/ContextInterface.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\Asset\Context; + +/** + * Holds information about the current request. + * + * @author Fabien Potencier + */ +interface ContextInterface +{ + /** + * Gets the base path. + * + * @return string The base path + */ + public function getBasePath(); + + /** + * Checks whether the request is secure or not. + * + * @return bool true if the request is secure, false otherwise + */ + public function isSecure(); +} diff --git a/vendor/symfony/asset/Context/NullContext.php b/vendor/symfony/asset/Context/NullContext.php new file mode 100644 index 00000000..0743d8cc --- /dev/null +++ b/vendor/symfony/asset/Context/NullContext.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\Asset\Context; + +/** + * A context that does nothing. + * + * @author Fabien Potencier + */ +class NullContext implements ContextInterface +{ + /** + * {@inheritdoc} + */ + public function getBasePath() + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function isSecure() + { + return false; + } +} diff --git a/vendor/symfony/asset/Context/RequestStackContext.php b/vendor/symfony/asset/Context/RequestStackContext.php new file mode 100644 index 00000000..ba711385 --- /dev/null +++ b/vendor/symfony/asset/Context/RequestStackContext.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\Asset\Context; + +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * Uses a RequestStack to populate the context. + * + * @author Fabien Potencier + */ +class RequestStackContext implements ContextInterface +{ + private $requestStack; + + public function __construct(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + } + + /** + * {@inheritdoc} + */ + public function getBasePath() + { + if (!$request = $this->requestStack->getMasterRequest()) { + return ''; + } + + return $request->getBasePath(); + } + + /** + * {@inheritdoc} + */ + public function isSecure() + { + if (!$request = $this->requestStack->getMasterRequest()) { + return false; + } + + return $request->isSecure(); + } +} diff --git a/vendor/symfony/asset/Exception/ExceptionInterface.php b/vendor/symfony/asset/Exception/ExceptionInterface.php new file mode 100644 index 00000000..cce1b5cc --- /dev/null +++ b/vendor/symfony/asset/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\Asset\Exception; + +/** + * Base ExceptionInterface for the Asset component. + * + * @author Fabien Potencier + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/asset/Exception/InvalidArgumentException.php b/vendor/symfony/asset/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..0945d8fa --- /dev/null +++ b/vendor/symfony/asset/Exception/InvalidArgumentException.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\Asset\Exception; + +/** + * Base InvalidArgumentException for the Asset component. + * + * @author Fabien Potencier + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/asset/Exception/LogicException.php b/vendor/symfony/asset/Exception/LogicException.php new file mode 100644 index 00000000..f291d88d --- /dev/null +++ b/vendor/symfony/asset/Exception/LogicException.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\Asset\Exception; + +/** + * Base LogicException for the Asset component. + * + * @author Fabien Potencier + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/asset/LICENSE b/vendor/symfony/asset/LICENSE new file mode 100644 index 00000000..17d16a13 --- /dev/null +++ b/vendor/symfony/asset/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/vendor/symfony/asset/Package.php b/vendor/symfony/asset/Package.php new file mode 100644 index 00000000..77b1c934 --- /dev/null +++ b/vendor/symfony/asset/Package.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\Asset; + +use Symfony\Component\Asset\Context\ContextInterface; +use Symfony\Component\Asset\Context\NullContext; +use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface; + +/** + * Basic package that adds a version to asset URLs. + * + * @author Kris Wallsmith + * @author Fabien Potencier + */ +class Package implements PackageInterface +{ + private $versionStrategy; + private $context; + + public function __construct(VersionStrategyInterface $versionStrategy, ContextInterface $context = null) + { + $this->versionStrategy = $versionStrategy; + $this->context = $context ?: new NullContext(); + } + + /** + * {@inheritdoc} + */ + public function getVersion($path) + { + return $this->versionStrategy->getVersion($path); + } + + /** + * {@inheritdoc} + */ + public function getUrl($path) + { + if ($this->isAbsoluteUrl($path)) { + return $path; + } + + return $this->versionStrategy->applyVersion($path); + } + + /** + * @return ContextInterface + */ + protected function getContext() + { + return $this->context; + } + + /** + * @return VersionStrategyInterface + */ + protected function getVersionStrategy() + { + return $this->versionStrategy; + } + + protected function isAbsoluteUrl($url) + { + return false !== strpos($url, '://') || '//' === substr($url, 0, 2); + } +} diff --git a/vendor/symfony/asset/PackageInterface.php b/vendor/symfony/asset/PackageInterface.php new file mode 100644 index 00000000..b9e9ff90 --- /dev/null +++ b/vendor/symfony/asset/PackageInterface.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\Asset; + +/** + * Asset package interface. + * + * @author Kris Wallsmith + */ +interface PackageInterface +{ + /** + * Returns the asset version for an asset. + * + * @param string $path A path + * + * @return string The version string + */ + public function getVersion($path); + + /** + * Returns an absolute or root-relative public path. + * + * @param string $path A path + * + * @return string The public path + */ + public function getUrl($path); +} diff --git a/vendor/symfony/asset/Packages.php b/vendor/symfony/asset/Packages.php new file mode 100644 index 00000000..0a8c9635 --- /dev/null +++ b/vendor/symfony/asset/Packages.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset; + +use Symfony\Component\Asset\Exception\InvalidArgumentException; +use Symfony\Component\Asset\Exception\LogicException; + +/** + * Helps manage asset URLs. + * + * @author Fabien Potencier + * @author Kris Wallsmith + */ +class Packages +{ + private $defaultPackage; + private $packages = array(); + + /** + * @param PackageInterface $defaultPackage The default package + * @param PackageInterface[] $packages Additional packages indexed by name + */ + public function __construct(PackageInterface $defaultPackage = null, array $packages = array()) + { + $this->defaultPackage = $defaultPackage; + + foreach ($packages as $name => $package) { + $this->addPackage($name, $package); + } + } + + /** + * Sets the default package. + * + * @param PackageInterface $defaultPackage The default package + */ + public function setDefaultPackage(PackageInterface $defaultPackage) + { + $this->defaultPackage = $defaultPackage; + } + + /** + * Adds a package. + * + * @param string $name The package name + * @param PackageInterface $package The package + */ + public function addPackage($name, PackageInterface $package) + { + $this->packages[$name] = $package; + } + + /** + * Returns an asset package. + * + * @param string $name The name of the package or null for the default package + * + * @return PackageInterface An asset package + * + * @throws InvalidArgumentException If there is no package by that name + * @throws LogicException If no default package is defined + */ + public function getPackage($name = null) + { + if (null === $name) { + if (null === $this->defaultPackage) { + throw new LogicException('There is no default asset package, configure one first.'); + } + + return $this->defaultPackage; + } + + if (!isset($this->packages[$name])) { + throw new InvalidArgumentException(sprintf('There is no "%s" asset package.', $name)); + } + + return $this->packages[$name]; + } + + /** + * Gets the version to add to public URL. + * + * @param string $path A public path + * @param string $packageName A package name + * + * @return string The current version + */ + public function getVersion($path, $packageName = null) + { + return $this->getPackage($packageName)->getVersion($path); + } + + /** + * Returns the public path. + * + * Absolute paths (i.e. http://...) are returned unmodified. + * + * @param string $path A public path + * @param string $packageName The name of the asset package to use + * + * @return string A public path which takes into account the base path and URL path + */ + public function getUrl($path, $packageName = null) + { + return $this->getPackage($packageName)->getUrl($path); + } +} diff --git a/vendor/symfony/asset/PathPackage.php b/vendor/symfony/asset/PathPackage.php new file mode 100644 index 00000000..31cbc5df --- /dev/null +++ b/vendor/symfony/asset/PathPackage.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset; + +use Symfony\Component\Asset\Context\ContextInterface; +use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface; + +/** + * Package that adds a base path to asset URLs in addition to a version. + * + * In addition to the provided base path, this package also automatically + * prepends the current request base path if a Context is available to + * allow a website to be hosted easily under any given path under the Web + * Server root directory. + * + * @author Fabien Potencier + */ +class PathPackage extends Package +{ + private $basePath; + + /** + * @param string $basePath The base path to be prepended to relative paths + * @param VersionStrategyInterface $versionStrategy The version strategy + * @param ContextInterface|null $context The context + */ + public function __construct($basePath, VersionStrategyInterface $versionStrategy, ContextInterface $context = null) + { + parent::__construct($versionStrategy, $context); + + if (!$basePath) { + $this->basePath = '/'; + } else { + if ('/' != $basePath[0]) { + $basePath = '/'.$basePath; + } + + $this->basePath = rtrim($basePath, '/').'/'; + } + } + + /** + * {@inheritdoc} + */ + public function getUrl($path) + { + if ($this->isAbsoluteUrl($path)) { + return $path; + } + + $versionedPath = $this->getVersionStrategy()->applyVersion($path); + + // if absolute or begins with /, we're done + if ($this->isAbsoluteUrl($versionedPath) || ($versionedPath && '/' === $versionedPath[0])) { + return $versionedPath; + } + + return $this->getBasePath().ltrim($versionedPath, '/'); + } + + /** + * Returns the base path. + * + * @return string The base path + */ + public function getBasePath() + { + return $this->getContext()->getBasePath().$this->basePath; + } +} diff --git a/vendor/symfony/asset/README.md b/vendor/symfony/asset/README.md new file mode 100644 index 00000000..32916984 --- /dev/null +++ b/vendor/symfony/asset/README.md @@ -0,0 +1,14 @@ +Asset Component +=============== + +The Asset component manages URL generation and versioning of web assets such as +CSS stylesheets, JavaScript files and image files. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/asset/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/vendor/symfony/asset/Tests/Context/NullContextTest.php b/vendor/symfony/asset/Tests/Context/NullContextTest.php new file mode 100644 index 00000000..4623412f --- /dev/null +++ b/vendor/symfony/asset/Tests/Context/NullContextTest.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\Asset\Tests\Context; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Asset\Context\NullContext; + +class NullContextTest extends TestCase +{ + public function testGetBasePath() + { + $nullContext = new NullContext(); + + $this->assertEmpty($nullContext->getBasePath()); + } + + public function testIsSecure() + { + $nullContext = new NullContext(); + + $this->assertFalse($nullContext->isSecure()); + } +} diff --git a/vendor/symfony/asset/Tests/Context/RequestStackContextTest.php b/vendor/symfony/asset/Tests/Context/RequestStackContextTest.php new file mode 100644 index 00000000..7269d0e6 --- /dev/null +++ b/vendor/symfony/asset/Tests/Context/RequestStackContextTest.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\Asset\Tests\Context; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Asset\Context\RequestStackContext; + +class RequestStackContextTest extends TestCase +{ + public function testGetBasePathEmpty() + { + $requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock(); + $requestStackContext = new RequestStackContext($requestStack); + + $this->assertEmpty($requestStackContext->getBasePath()); + } + + public function testGetBasePathSet() + { + $testBasePath = 'test-path'; + + $request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->getMock(); + $request->method('getBasePath') + ->willReturn($testBasePath); + $requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock(); + $requestStack->method('getMasterRequest') + ->willReturn($request); + + $requestStackContext = new RequestStackContext($requestStack); + + $this->assertEquals($testBasePath, $requestStackContext->getBasePath()); + } + + public function testIsSecureFalse() + { + $requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock(); + $requestStackContext = new RequestStackContext($requestStack); + + $this->assertFalse($requestStackContext->isSecure()); + } + + public function testIsSecureTrue() + { + $request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->getMock(); + $request->method('isSecure') + ->willReturn(true); + $requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock(); + $requestStack->method('getMasterRequest') + ->willReturn($request); + + $requestStackContext = new RequestStackContext($requestStack); + + $this->assertTrue($requestStackContext->isSecure()); + } +} diff --git a/vendor/symfony/asset/Tests/PackageTest.php b/vendor/symfony/asset/Tests/PackageTest.php new file mode 100644 index 00000000..9a171966 --- /dev/null +++ b/vendor/symfony/asset/Tests/PackageTest.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\Asset\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Asset\Package; +use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy; +use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy; + +class PackageTest extends TestCase +{ + /** + * @dataProvider getConfigs + */ + public function testGetUrl($version, $format, $path, $expected) + { + $package = new Package($version ? new StaticVersionStrategy($version, $format) : new EmptyVersionStrategy()); + $this->assertEquals($expected, $package->getUrl($path)); + } + + public function getConfigs() + { + return array( + array('v1', '', 'http://example.com/foo', 'http://example.com/foo'), + array('v1', '', 'https://example.com/foo', 'https://example.com/foo'), + array('v1', '', '//example.com/foo', '//example.com/foo'), + + array('v1', '', '/foo', '/foo?v1'), + array('v1', '', 'foo', 'foo?v1'), + + array(null, '', '/foo', '/foo'), + array(null, '', 'foo', 'foo'), + + array('v1', 'version-%2$s/%1$s', '/foo', '/version-v1/foo'), + array('v1', 'version-%2$s/%1$s', 'foo', 'version-v1/foo'), + array('v1', 'version-%2$s/%1$s', 'foo/', 'version-v1/foo/'), + array('v1', 'version-%2$s/%1$s', '/foo/', '/version-v1/foo/'), + ); + } + + public function testGetVersion() + { + $package = new Package(new StaticVersionStrategy('v1')); + $this->assertEquals('v1', $package->getVersion('/foo')); + } +} diff --git a/vendor/symfony/asset/Tests/PackagesTest.php b/vendor/symfony/asset/Tests/PackagesTest.php new file mode 100644 index 00000000..4b0872f7 --- /dev/null +++ b/vendor/symfony/asset/Tests/PackagesTest.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\Asset\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Asset\Package; +use Symfony\Component\Asset\Packages; +use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy; + +class PackagesTest extends TestCase +{ + public function testGetterSetters() + { + $packages = new Packages(); + $packages->setDefaultPackage($default = $this->getMockBuilder('Symfony\Component\Asset\PackageInterface')->getMock()); + $packages->addPackage('a', $a = $this->getMockBuilder('Symfony\Component\Asset\PackageInterface')->getMock()); + + $this->assertEquals($default, $packages->getPackage()); + $this->assertEquals($a, $packages->getPackage('a')); + + $packages = new Packages($default, array('a' => $a)); + + $this->assertEquals($default, $packages->getPackage()); + $this->assertEquals($a, $packages->getPackage('a')); + } + + public function testGetVersion() + { + $packages = new Packages( + new Package(new StaticVersionStrategy('default')), + array('a' => new Package(new StaticVersionStrategy('a'))) + ); + + $this->assertEquals('default', $packages->getVersion('/foo')); + $this->assertEquals('a', $packages->getVersion('/foo', 'a')); + } + + public function testGetUrl() + { + $packages = new Packages( + new Package(new StaticVersionStrategy('default')), + array('a' => new Package(new StaticVersionStrategy('a'))) + ); + + $this->assertEquals('/foo?default', $packages->getUrl('/foo')); + $this->assertEquals('/foo?a', $packages->getUrl('/foo', 'a')); + } + + /** + * @expectedException \Symfony\Component\Asset\Exception\LogicException + */ + public function testNoDefaultPackage() + { + $packages = new Packages(); + $packages->getPackage(); + } + + /** + * @expectedException \Symfony\Component\Asset\Exception\InvalidArgumentException + */ + public function testUndefinedPackage() + { + $packages = new Packages(); + $packages->getPackage('a'); + } +} diff --git a/vendor/symfony/asset/Tests/PathPackageTest.php b/vendor/symfony/asset/Tests/PathPackageTest.php new file mode 100644 index 00000000..4a9f90df --- /dev/null +++ b/vendor/symfony/asset/Tests/PathPackageTest.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Asset\PathPackage; +use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy; + +class PathPackageTest extends TestCase +{ + /** + * @dataProvider getConfigs + */ + public function testGetUrl($basePath, $format, $path, $expected) + { + $package = new PathPackage($basePath, new StaticVersionStrategy('v1', $format)); + $this->assertEquals($expected, $package->getUrl($path)); + } + + public function getConfigs() + { + return array( + array('/foo', '', 'http://example.com/foo', 'http://example.com/foo'), + array('/foo', '', 'https://example.com/foo', 'https://example.com/foo'), + array('/foo', '', '//example.com/foo', '//example.com/foo'), + + array('', '', '/foo', '/foo?v1'), + + array('/foo', '', '/bar', '/bar?v1'), + array('/foo', '', 'bar', '/foo/bar?v1'), + array('foo', '', 'bar', '/foo/bar?v1'), + array('foo/', '', 'bar', '/foo/bar?v1'), + array('/foo/', '', 'bar', '/foo/bar?v1'), + + array('/foo', 'version-%2$s/%1$s', '/bar', '/version-v1/bar'), + array('/foo', 'version-%2$s/%1$s', 'bar', '/foo/version-v1/bar'), + array('/foo', 'version-%2$s/%1$s', 'bar/', '/foo/version-v1/bar/'), + array('/foo', 'version-%2$s/%1$s', '/bar/', '/version-v1/bar/'), + ); + } + + /** + * @dataProvider getContextConfigs + */ + public function testGetUrlWithContext($basePathRequest, $basePath, $format, $path, $expected) + { + $package = new PathPackage($basePath, new StaticVersionStrategy('v1', $format), $this->getContext($basePathRequest)); + + $this->assertEquals($expected, $package->getUrl($path)); + } + + public function getContextConfigs() + { + return array( + array('', '/foo', '', '/baz', '/baz?v1'), + array('', '/foo', '', 'baz', '/foo/baz?v1'), + array('', 'foo', '', 'baz', '/foo/baz?v1'), + array('', 'foo/', '', 'baz', '/foo/baz?v1'), + array('', '/foo/', '', 'baz', '/foo/baz?v1'), + + array('/bar', '/foo', '', '/baz', '/baz?v1'), + array('/bar', '/foo', '', 'baz', '/bar/foo/baz?v1'), + array('/bar', 'foo', '', 'baz', '/bar/foo/baz?v1'), + array('/bar', 'foo/', '', 'baz', '/bar/foo/baz?v1'), + array('/bar', '/foo/', '', 'baz', '/bar/foo/baz?v1'), + ); + } + + public function testVersionStrategyGivesAbsoluteURL() + { + $versionStrategy = $this->getMockBuilder('Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface')->getMock(); + $versionStrategy->expects($this->any()) + ->method('applyVersion') + ->willReturn('https://cdn.com/bar/main.css'); + $package = new PathPackage('/subdirectory', $versionStrategy, $this->getContext('/bar')); + + $this->assertEquals('https://cdn.com/bar/main.css', $package->getUrl('main.css')); + } + + private function getContext($basePath) + { + $context = $this->getMockBuilder('Symfony\Component\Asset\Context\ContextInterface')->getMock(); + $context->expects($this->any())->method('getBasePath')->will($this->returnValue($basePath)); + + return $context; + } +} diff --git a/vendor/symfony/asset/Tests/UrlPackageTest.php b/vendor/symfony/asset/Tests/UrlPackageTest.php new file mode 100644 index 00000000..97e7a46d --- /dev/null +++ b/vendor/symfony/asset/Tests/UrlPackageTest.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\Asset\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Asset\UrlPackage; +use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy; +use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy; + +class UrlPackageTest extends TestCase +{ + /** + * @dataProvider getConfigs + */ + public function testGetUrl($baseUrls, $format, $path, $expected) + { + $package = new UrlPackage($baseUrls, new StaticVersionStrategy('v1', $format)); + $this->assertEquals($expected, $package->getUrl($path)); + } + + public function getConfigs() + { + return array( + array('http://example.net', '', 'http://example.com/foo', 'http://example.com/foo'), + array('http://example.net', '', 'https://example.com/foo', 'https://example.com/foo'), + array('http://example.net', '', '//example.com/foo', '//example.com/foo'), + + array('http://example.com', '', '/foo', 'http://example.com/foo?v1'), + array('http://example.com', '', 'foo', 'http://example.com/foo?v1'), + array('http://example.com/', '', 'foo', 'http://example.com/foo?v1'), + array('http://example.com/foo', '', 'foo', 'http://example.com/foo/foo?v1'), + array('http://example.com/foo/', '', 'foo', 'http://example.com/foo/foo?v1'), + + array(array('http://example.com'), '', '/foo', 'http://example.com/foo?v1'), + array(array('http://example.com', 'http://example.net'), '', '/foo', 'http://example.com/foo?v1'), + array(array('http://example.com', 'http://example.net'), '', '/fooa', 'http://example.net/fooa?v1'), + + array('http://example.com', 'version-%2$s/%1$s', '/foo', 'http://example.com/version-v1/foo'), + array('http://example.com', 'version-%2$s/%1$s', 'foo', 'http://example.com/version-v1/foo'), + array('http://example.com', 'version-%2$s/%1$s', 'foo/', 'http://example.com/version-v1/foo/'), + array('http://example.com', 'version-%2$s/%1$s', '/foo/', 'http://example.com/version-v1/foo/'), + ); + } + + /** + * @dataProvider getContextConfigs + */ + public function testGetUrlWithContext($secure, $baseUrls, $format, $path, $expected) + { + $package = new UrlPackage($baseUrls, new StaticVersionStrategy('v1', $format), $this->getContext($secure)); + + $this->assertEquals($expected, $package->getUrl($path)); + } + + public function getContextConfigs() + { + return array( + array(false, 'http://example.com', '', 'foo', 'http://example.com/foo?v1'), + array(false, array('http://example.com'), '', 'foo', 'http://example.com/foo?v1'), + array(false, array('http://example.com', 'https://example.com'), '', 'foo', 'http://example.com/foo?v1'), + array(false, array('http://example.com', 'https://example.com'), '', 'fooa', 'https://example.com/fooa?v1'), + array(false, array('http://example.com/bar'), '', 'foo', 'http://example.com/bar/foo?v1'), + array(false, array('http://example.com/bar/'), '', 'foo', 'http://example.com/bar/foo?v1'), + array(false, array('//example.com/bar/'), '', 'foo', '//example.com/bar/foo?v1'), + + array(true, array('http://example.com'), '', 'foo', 'http://example.com/foo?v1'), + array(true, array('http://example.com', 'https://example.com'), '', 'foo', 'https://example.com/foo?v1'), + ); + } + + public function testVersionStrategyGivesAbsoluteURL() + { + $versionStrategy = $this->getMockBuilder('Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface')->getMock(); + $versionStrategy->expects($this->any()) + ->method('applyVersion') + ->willReturn('https://cdn.com/bar/main.css'); + $package = new UrlPackage('https://example.com', $versionStrategy); + + $this->assertEquals('https://cdn.com/bar/main.css', $package->getUrl('main.css')); + } + + /** + * @expectedException \Symfony\Component\Asset\Exception\LogicException + */ + public function testNoBaseUrls() + { + new UrlPackage(array(), new EmptyVersionStrategy()); + } + + /** + * @expectedException \Symfony\Component\Asset\Exception\InvalidArgumentException + */ + public function testWrongBaseUrl() + { + new UrlPackage(array('not-a-url'), new EmptyVersionStrategy()); + } + + private function getContext($secure) + { + $context = $this->getMockBuilder('Symfony\Component\Asset\Context\ContextInterface')->getMock(); + $context->expects($this->any())->method('isSecure')->will($this->returnValue($secure)); + + return $context; + } +} diff --git a/vendor/symfony/asset/Tests/VersionStrategy/EmptyVersionStrategyTest.php b/vendor/symfony/asset/Tests/VersionStrategy/EmptyVersionStrategyTest.php new file mode 100644 index 00000000..430146fd --- /dev/null +++ b/vendor/symfony/asset/Tests/VersionStrategy/EmptyVersionStrategyTest.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\Asset\Tests\VersionStrategy; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy; + +class EmptyVersionStrategyTest extends TestCase +{ + public function testGetVersion() + { + $emptyVersionStrategy = new EmptyVersionStrategy(); + $path = 'test-path'; + + $this->assertEmpty($emptyVersionStrategy->getVersion($path)); + } + + public function testApplyVersion() + { + $emptyVersionStrategy = new EmptyVersionStrategy(); + $path = 'test-path'; + + $this->assertEquals($path, $emptyVersionStrategy->applyVersion($path)); + } +} diff --git a/vendor/symfony/asset/Tests/VersionStrategy/JsonManifestVersionStrategyTest.php b/vendor/symfony/asset/Tests/VersionStrategy/JsonManifestVersionStrategyTest.php new file mode 100644 index 00000000..9da2b4ad --- /dev/null +++ b/vendor/symfony/asset/Tests/VersionStrategy/JsonManifestVersionStrategyTest.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\Asset\Tests\VersionStrategy; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy; + +class JsonManifestVersionStrategyTest extends TestCase +{ + public function testGetVersion() + { + $strategy = $this->createStrategy('manifest-valid.json'); + + $this->assertEquals('main.123abc.js', $strategy->getVersion('main.js')); + } + + public function testApplyVersion() + { + $strategy = $this->createStrategy('manifest-valid.json'); + + $this->assertEquals('css/styles.555def.css', $strategy->getVersion('css/styles.css')); + } + + public function testApplyVersionWhenKeyDoesNotExistInManifest() + { + $strategy = $this->createStrategy('manifest-valid.json'); + + $this->assertEquals('css/other.css', $strategy->getVersion('css/other.css')); + } + + /** + * @expectedException \RuntimeException + */ + public function testMissingManifestFileThrowsException() + { + $strategy = $this->createStrategy('non-existent-file.json'); + $strategy->getVersion('main.js'); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Error parsing JSON + */ + public function testManifestFileWithBadJSONThrowsException() + { + $strategy = $this->createStrategy('manifest-invalid.json'); + $strategy->getVersion('main.js'); + } + + private function createStrategy($manifestFilename) + { + return new JsonManifestVersionStrategy(__DIR__.'/../fixtures/'.$manifestFilename); + } +} diff --git a/vendor/symfony/asset/Tests/VersionStrategy/StaticVersionStrategyTest.php b/vendor/symfony/asset/Tests/VersionStrategy/StaticVersionStrategyTest.php new file mode 100644 index 00000000..7b5a3e8d --- /dev/null +++ b/vendor/symfony/asset/Tests/VersionStrategy/StaticVersionStrategyTest.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\Asset\Tests\VersionStrategy; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy; + +class StaticVersionStrategyTest extends TestCase +{ + public function testGetVersion() + { + $version = 'v1'; + $path = 'test-path'; + $staticVersionStrategy = new StaticVersionStrategy($version); + $this->assertEquals($version, $staticVersionStrategy->getVersion($path)); + } + + /** + * @dataProvider getConfigs + */ + public function testApplyVersion($path, $version, $format) + { + $staticVersionStrategy = new StaticVersionStrategy($version, $format); + $formatted = sprintf($format ?: '%s?%s', $path, $version); + $this->assertEquals($formatted, $staticVersionStrategy->applyVersion($path)); + } + + public function getConfigs() + { + return array( + array('test-path', 'v1', null), + array('test-path', 'v2', '%s?test%s'), + ); + } +} diff --git a/vendor/symfony/asset/Tests/fixtures/manifest-invalid.json b/vendor/symfony/asset/Tests/fixtures/manifest-invalid.json new file mode 100644 index 00000000..feed937e --- /dev/null +++ b/vendor/symfony/asset/Tests/fixtures/manifest-invalid.json @@ -0,0 +1,4 @@ +{ + "main.js": main.123abc.js", + "css/styles.css": "css/styles.555def.css" +} diff --git a/vendor/symfony/asset/Tests/fixtures/manifest-valid.json b/vendor/symfony/asset/Tests/fixtures/manifest-valid.json new file mode 100644 index 00000000..546a0066 --- /dev/null +++ b/vendor/symfony/asset/Tests/fixtures/manifest-valid.json @@ -0,0 +1,4 @@ +{ + "main.js": "main.123abc.js", + "css/styles.css": "css/styles.555def.css" +} diff --git a/vendor/symfony/asset/UrlPackage.php b/vendor/symfony/asset/UrlPackage.php new file mode 100644 index 00000000..782b2ddf --- /dev/null +++ b/vendor/symfony/asset/UrlPackage.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset; + +use Symfony\Component\Asset\Context\ContextInterface; +use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface; +use Symfony\Component\Asset\Exception\InvalidArgumentException; +use Symfony\Component\Asset\Exception\LogicException; + +/** + * Package that adds a base URL to asset URLs in addition to a version. + * + * The package allows to use more than one base URLs in which case + * it randomly chooses one for each asset; it also guarantees that + * any given path will always use the same base URL to be nice with + * HTTP caching mechanisms. + * + * When the request context is available, this package can choose the + * best base URL to use based on the current request scheme: + * + * * For HTTP request, it chooses between all base URLs; + * * For HTTPs requests, it chooses between HTTPs base URLs and relative protocol URLs + * or falls back to any base URL if no secure ones are available. + * + * @author Fabien Potencier + */ +class UrlPackage extends Package +{ + private $baseUrls = array(); + private $sslPackage; + + /** + * @param string|string[] $baseUrls Base asset URLs + * @param VersionStrategyInterface $versionStrategy The version strategy + * @param ContextInterface|null $context Context + */ + public function __construct($baseUrls, VersionStrategyInterface $versionStrategy, ContextInterface $context = null) + { + parent::__construct($versionStrategy, $context); + + if (!is_array($baseUrls)) { + $baseUrls = (array) $baseUrls; + } + + if (!$baseUrls) { + throw new LogicException('You must provide at least one base URL.'); + } + + foreach ($baseUrls as $baseUrl) { + $this->baseUrls[] = rtrim($baseUrl, '/'); + } + + $sslUrls = $this->getSslUrls($baseUrls); + + if ($sslUrls && $baseUrls !== $sslUrls) { + $this->sslPackage = new self($sslUrls, $versionStrategy); + } + } + + /** + * {@inheritdoc} + */ + public function getUrl($path) + { + if ($this->isAbsoluteUrl($path)) { + return $path; + } + + if (null !== $this->sslPackage && $this->getContext()->isSecure()) { + return $this->sslPackage->getUrl($path); + } + + $url = $this->getVersionStrategy()->applyVersion($path); + + if ($this->isAbsoluteUrl($url)) { + return $url; + } + + if ($url && '/' != $url[0]) { + $url = '/'.$url; + } + + return $this->getBaseUrl($path).$url; + } + + /** + * Returns the base URL for a path. + * + * @param string $path + * + * @return string The base URL + */ + public function getBaseUrl($path) + { + if (1 === count($this->baseUrls)) { + return $this->baseUrls[0]; + } + + return $this->baseUrls[$this->chooseBaseUrl($path)]; + } + + /** + * Determines which base URL to use for the given path. + * + * Override this method to change the default distribution strategy. + * This method should always return the same base URL index for a given path. + * + * @param string $path + * + * @return int The base URL index for the given path + */ + protected function chooseBaseUrl($path) + { + return (int) fmod(hexdec(substr(hash('sha256', $path), 0, 10)), count($this->baseUrls)); + } + + private function getSslUrls($urls) + { + $sslUrls = array(); + foreach ($urls as $url) { + if ('https://' === substr($url, 0, 8) || '//' === substr($url, 0, 2)) { + $sslUrls[] = $url; + } elseif ('http://' !== substr($url, 0, 7)) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid URL', $url)); + } + } + + return $sslUrls; + } +} diff --git a/vendor/symfony/asset/VersionStrategy/EmptyVersionStrategy.php b/vendor/symfony/asset/VersionStrategy/EmptyVersionStrategy.php new file mode 100644 index 00000000..aa06eaa5 --- /dev/null +++ b/vendor/symfony/asset/VersionStrategy/EmptyVersionStrategy.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\Asset\VersionStrategy; + +/** + * Disable version for all assets. + * + * @author Fabien Potencier + */ +class EmptyVersionStrategy implements VersionStrategyInterface +{ + /** + * {@inheritdoc} + */ + public function getVersion($path) + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function applyVersion($path) + { + return $path; + } +} diff --git a/vendor/symfony/asset/VersionStrategy/JsonManifestVersionStrategy.php b/vendor/symfony/asset/VersionStrategy/JsonManifestVersionStrategy.php new file mode 100644 index 00000000..378ad543 --- /dev/null +++ b/vendor/symfony/asset/VersionStrategy/JsonManifestVersionStrategy.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\Asset\VersionStrategy; + +/** + * Reads the versioned path of an asset from a JSON manifest file. + * + * For example, the manifest file might look like this: + * { + * "main.js": "main.abc123.js", + * "css/styles.css": "css/styles.555abc.css" + * } + * + * You could then ask for the version of "main.js" or "css/styles.css". + */ +class JsonManifestVersionStrategy implements VersionStrategyInterface +{ + private $manifestPath; + private $manifestData; + + /** + * @param string $manifestPath Absolute path to the manifest file + */ + public function __construct($manifestPath) + { + $this->manifestPath = $manifestPath; + } + + /** + * With a manifest, we don't really know or care about what + * the version is. Instead, this returns the path to the + * versioned file. + */ + public function getVersion($path) + { + return $this->applyVersion($path); + } + + public function applyVersion($path) + { + return $this->getManifestPath($path) ?: $path; + } + + private function getManifestPath($path) + { + if (null === $this->manifestData) { + if (!file_exists($this->manifestPath)) { + throw new \RuntimeException(sprintf('Asset manifest file "%s" does not exist.', $this->manifestPath)); + } + + $this->manifestData = json_decode(file_get_contents($this->manifestPath), true); + if (0 < json_last_error()) { + throw new \RuntimeException(sprintf('Error parsing JSON from asset manifest file "%s" - %s', $this->manifestPath, json_last_error_msg())); + } + } + + return isset($this->manifestData[$path]) ? $this->manifestData[$path] : null; + } +} diff --git a/vendor/symfony/asset/VersionStrategy/StaticVersionStrategy.php b/vendor/symfony/asset/VersionStrategy/StaticVersionStrategy.php new file mode 100644 index 00000000..857cf943 --- /dev/null +++ b/vendor/symfony/asset/VersionStrategy/StaticVersionStrategy.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\Asset\VersionStrategy; + +/** + * Returns the same version for all assets. + * + * @author Fabien Potencier + */ +class StaticVersionStrategy implements VersionStrategyInterface +{ + private $version; + private $format; + + /** + * @param string $version Version number + * @param string $format Url format + */ + public function __construct($version, $format = null) + { + $this->version = $version; + $this->format = $format ?: '%s?%s'; + } + + /** + * {@inheritdoc} + */ + public function getVersion($path) + { + return $this->version; + } + + /** + * {@inheritdoc} + */ + public function applyVersion($path) + { + $versionized = sprintf($this->format, ltrim($path, '/'), $this->getVersion($path)); + + if ($path && '/' == $path[0]) { + return '/'.$versionized; + } + + return $versionized; + } +} diff --git a/vendor/symfony/asset/VersionStrategy/VersionStrategyInterface.php b/vendor/symfony/asset/VersionStrategy/VersionStrategyInterface.php new file mode 100644 index 00000000..a0fb2600 --- /dev/null +++ b/vendor/symfony/asset/VersionStrategy/VersionStrategyInterface.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\Asset\VersionStrategy; + +/** + * Asset version strategy interface. + * + * @author Fabien Potencier + */ +interface VersionStrategyInterface +{ + /** + * Returns the asset version for an asset. + * + * @param string $path A path + * + * @return string The version string + */ + public function getVersion($path); + + /** + * Applies version to the supplied path. + * + * @param string $path A path + * + * @return string The versionized path + */ + public function applyVersion($path); +} diff --git a/vendor/symfony/asset/composer.json b/vendor/symfony/asset/composer.json new file mode 100644 index 00000000..8ed8d9d7 --- /dev/null +++ b/vendor/symfony/asset/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/asset", + "type": "library", + "description": "Symfony Asset 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" + }, + "suggest": { + "symfony/http-foundation": "" + }, + "require-dev": { + "symfony/http-foundation": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Asset\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/vendor/symfony/asset/phpunit.xml.dist b/vendor/symfony/asset/phpunit.xml.dist new file mode 100644 index 00000000..906578a8 --- /dev/null +++ b/vendor/symfony/asset/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/browser-kit/.gitignore b/vendor/symfony/browser-kit/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/browser-kit/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/browser-kit/CHANGELOG.md b/vendor/symfony/browser-kit/CHANGELOG.md new file mode 100644 index 00000000..036595c9 --- /dev/null +++ b/vendor/symfony/browser-kit/CHANGELOG.md @@ -0,0 +1,29 @@ +CHANGELOG +========= + +3.3.0 +----- + + * [BC BREAK] The request method is dropped from POST to GET when the response + status code is 301. + +3.2.0 +----- + + * Client HTTP user agent has been changed to 'Symfony BrowserKit' + +2.3.0 +----- + + * [BC BREAK] `Client::followRedirect()` won't redirect responses with + a non-3xx Status Code and `Location` header anymore, as per + http://tools.ietf.org/html/rfc2616#section-14.30 + + * added `Client::getInternalRequest()` and `Client::getInternalResponse()` to + have access to the BrowserKit internal request and response objects + +2.1.0 +----- + + * [BC BREAK] The CookieJar internals have changed to allow cookies with the + same name on different sub-domains/sub-paths diff --git a/vendor/symfony/browser-kit/Client.php b/vendor/symfony/browser-kit/Client.php new file mode 100644 index 00000000..286f2f98 --- /dev/null +++ b/vendor/symfony/browser-kit/Client.php @@ -0,0 +1,596 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit; + +use Symfony\Component\DomCrawler\Crawler; +use Symfony\Component\DomCrawler\Link; +use Symfony\Component\DomCrawler\Form; +use Symfony\Component\Process\PhpProcess; + +/** + * Client simulates a browser. + * + * To make the actual request, you need to implement the doRequest() method. + * + * If you want to be able to run requests in their own process (insulated flag), + * you need to also implement the getScript() method. + * + * @author Fabien Potencier + */ +abstract class Client +{ + protected $history; + protected $cookieJar; + protected $server = array(); + protected $internalRequest; + protected $request; + protected $internalResponse; + protected $response; + protected $crawler; + protected $insulated = false; + protected $redirect; + protected $followRedirects = true; + + private $maxRedirects = -1; + private $redirectCount = 0; + private $isMainRequest = true; + + /** + * Constructor. + * + * @param array $server The server parameters (equivalent of $_SERVER) + * @param History $history A History instance to store the browser history + * @param CookieJar $cookieJar A CookieJar instance to store the cookies + */ + public function __construct(array $server = array(), History $history = null, CookieJar $cookieJar = null) + { + $this->setServerParameters($server); + $this->history = $history ?: new History(); + $this->cookieJar = $cookieJar ?: new CookieJar(); + } + + /** + * Sets whether to automatically follow redirects or not. + * + * @param bool $followRedirect Whether to follow redirects + */ + public function followRedirects($followRedirect = true) + { + $this->followRedirects = (bool) $followRedirect; + } + + /** + * Returns whether client automatically follows redirects or not. + * + * @return bool + */ + public function isFollowingRedirects() + { + return $this->followRedirects; + } + + /** + * Sets the maximum number of requests that crawler can follow. + * + * @param int $maxRedirects + */ + public function setMaxRedirects($maxRedirects) + { + $this->maxRedirects = $maxRedirects < 0 ? -1 : $maxRedirects; + $this->followRedirects = -1 != $this->maxRedirects; + } + + /** + * Returns the maximum number of requests that crawler can follow. + * + * @return int + */ + public function getMaxRedirects() + { + return $this->maxRedirects; + } + + /** + * Sets the insulated flag. + * + * @param bool $insulated Whether to insulate the requests or not + * + * @throws \RuntimeException When Symfony Process Component is not installed + */ + public function insulate($insulated = true) + { + if ($insulated && !class_exists('Symfony\\Component\\Process\\Process')) { + throw new \RuntimeException('Unable to isolate requests as the Symfony Process Component is not installed.'); + } + + $this->insulated = (bool) $insulated; + } + + /** + * Sets server parameters. + * + * @param array $server An array of server parameters + */ + public function setServerParameters(array $server) + { + $this->server = array_merge(array( + 'HTTP_USER_AGENT' => 'Symfony BrowserKit', + ), $server); + } + + /** + * Sets single server parameter. + * + * @param string $key A key of the parameter + * @param string $value A value of the parameter + */ + public function setServerParameter($key, $value) + { + $this->server[$key] = $value; + } + + /** + * Gets single server parameter for specified key. + * + * @param string $key A key of the parameter to get + * @param string $default A default value when key is undefined + * + * @return string A value of the parameter + */ + public function getServerParameter($key, $default = '') + { + return isset($this->server[$key]) ? $this->server[$key] : $default; + } + + /** + * Returns the History instance. + * + * @return History A History instance + */ + public function getHistory() + { + return $this->history; + } + + /** + * Returns the CookieJar instance. + * + * @return CookieJar A CookieJar instance + */ + public function getCookieJar() + { + return $this->cookieJar; + } + + /** + * Returns the current Crawler instance. + * + * @return Crawler|null A Crawler instance + */ + public function getCrawler() + { + return $this->crawler; + } + + /** + * Returns the current BrowserKit Response instance. + * + * @return Response|null A BrowserKit Response instance + */ + public function getInternalResponse() + { + return $this->internalResponse; + } + + /** + * Returns the current origin response instance. + * + * The origin response is the response instance that is returned + * by the code that handles requests. + * + * @return object|null A response instance + * + * @see doRequest() + */ + public function getResponse() + { + return $this->response; + } + + /** + * Returns the current BrowserKit Request instance. + * + * @return Request|null A BrowserKit Request instance + */ + public function getInternalRequest() + { + return $this->internalRequest; + } + + /** + * Returns the current origin Request instance. + * + * The origin request is the request instance that is sent + * to the code that handles requests. + * + * @return object|null A Request instance + * + * @see doRequest() + */ + public function getRequest() + { + return $this->request; + } + + /** + * Clicks on a given link. + * + * @param Link $link A Link instance + * + * @return Crawler + */ + public function click(Link $link) + { + if ($link instanceof Form) { + return $this->submit($link); + } + + return $this->request($link->getMethod(), $link->getUri()); + } + + /** + * Submits a form. + * + * @param Form $form A Form instance + * @param array $values An array of form field values + * + * @return Crawler + */ + public function submit(Form $form, array $values = array()) + { + $form->setValues($values); + + return $this->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $form->getPhpFiles()); + } + + /** + * Calls a URI. + * + * @param string $method The request method + * @param string $uri The URI to fetch + * @param array $parameters The Request parameters + * @param array $files The files + * @param array $server The server parameters (HTTP headers are referenced with a HTTP_ prefix as PHP does) + * @param string $content The raw body data + * @param bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload()) + * + * @return Crawler + */ + public function request($method, $uri, array $parameters = array(), array $files = array(), array $server = array(), $content = null, $changeHistory = true) + { + if ($this->isMainRequest) { + $this->redirectCount = 0; + } else { + ++$this->redirectCount; + } + + $uri = $this->getAbsoluteUri($uri); + + $server = array_merge($this->server, $server); + + if (isset($server['HTTPS'])) { + $uri = preg_replace('{^'.parse_url($uri, PHP_URL_SCHEME).'}', $server['HTTPS'] ? 'https' : 'http', $uri); + } + + if (!$this->history->isEmpty()) { + $server['HTTP_REFERER'] = $this->history->current()->getUri(); + } + + if (empty($server['HTTP_HOST'])) { + $server['HTTP_HOST'] = $this->extractHost($uri); + } + + $server['HTTPS'] = 'https' == parse_url($uri, PHP_URL_SCHEME); + + $this->internalRequest = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content); + + $this->request = $this->filterRequest($this->internalRequest); + + if (true === $changeHistory) { + $this->history->add($this->internalRequest); + } + + if ($this->insulated) { + $this->response = $this->doRequestInProcess($this->request); + } else { + $this->response = $this->doRequest($this->request); + } + + $this->internalResponse = $this->filterResponse($this->response); + + $this->cookieJar->updateFromResponse($this->internalResponse, $uri); + + $status = $this->internalResponse->getStatus(); + + if ($status >= 300 && $status < 400) { + $this->redirect = $this->internalResponse->getHeader('Location'); + } else { + $this->redirect = null; + } + + if ($this->followRedirects && $this->redirect) { + return $this->crawler = $this->followRedirect(); + } + + return $this->crawler = $this->createCrawlerFromContent($this->internalRequest->getUri(), $this->internalResponse->getContent(), $this->internalResponse->getHeader('Content-Type')); + } + + /** + * Makes a request in another process. + * + * @param object $request An origin request instance + * + * @return object An origin response instance + * + * @throws \RuntimeException When processing returns exit code + */ + protected function doRequestInProcess($request) + { + $process = new PhpProcess($this->getScript($request), null, null); + $process->run(); + + if (!$process->isSuccessful() || !preg_match('/^O\:\d+\:/', $process->getOutput())) { + throw new \RuntimeException(sprintf('OUTPUT: %s ERROR OUTPUT: %s', $process->getOutput(), $process->getErrorOutput())); + } + + return unserialize($process->getOutput()); + } + + /** + * Makes a request. + * + * @param object $request An origin request instance + * + * @return object An origin response instance + */ + abstract protected function doRequest($request); + + /** + * Returns the script to execute when the request must be insulated. + * + * @param object $request An origin request instance + * + * @throws \LogicException When this abstract class is not implemented + */ + protected function getScript($request) + { + throw new \LogicException('To insulate requests, you need to override the getScript() method.'); + } + + /** + * Filters the BrowserKit request to the origin one. + * + * @param Request $request The BrowserKit Request to filter + * + * @return object An origin request instance + */ + protected function filterRequest(Request $request) + { + return $request; + } + + /** + * Filters the origin response to the BrowserKit one. + * + * @param object $response The origin response to filter + * + * @return Response An BrowserKit Response instance + */ + protected function filterResponse($response) + { + return $response; + } + + /** + * Creates a crawler. + * + * This method returns null if the DomCrawler component is not available. + * + * @param string $uri A URI + * @param string $content Content for the crawler to use + * @param string $type Content type + * + * @return Crawler|null + */ + protected function createCrawlerFromContent($uri, $content, $type) + { + if (!class_exists('Symfony\Component\DomCrawler\Crawler')) { + return; + } + + $crawler = new Crawler(null, $uri); + $crawler->addContent($content, $type); + + return $crawler; + } + + /** + * Goes back in the browser history. + * + * @return Crawler + */ + public function back() + { + return $this->requestFromRequest($this->history->back(), false); + } + + /** + * Goes forward in the browser history. + * + * @return Crawler + */ + public function forward() + { + return $this->requestFromRequest($this->history->forward(), false); + } + + /** + * Reloads the current browser. + * + * @return Crawler + */ + public function reload() + { + return $this->requestFromRequest($this->history->current(), false); + } + + /** + * Follow redirects? + * + * @return Crawler + * + * @throws \LogicException If request was not a redirect + */ + public function followRedirect() + { + if (empty($this->redirect)) { + throw new \LogicException('The request was not redirected.'); + } + + if (-1 !== $this->maxRedirects) { + if ($this->redirectCount > $this->maxRedirects) { + throw new \LogicException(sprintf('The maximum number (%d) of redirections was reached.', $this->maxRedirects)); + } + } + + $request = $this->internalRequest; + + if (in_array($this->internalResponse->getStatus(), array(301, 302, 303))) { + $method = 'GET'; + $files = array(); + $content = null; + } else { + $method = $request->getMethod(); + $files = $request->getFiles(); + $content = $request->getContent(); + } + + if ('GET' === strtoupper($method)) { + // Don't forward parameters for GET request as it should reach the redirection URI + $parameters = array(); + } else { + $parameters = $request->getParameters(); + } + + $server = $request->getServer(); + $server = $this->updateServerFromUri($server, $this->redirect); + + $this->isMainRequest = false; + + $response = $this->request($method, $this->redirect, $parameters, $files, $server, $content); + + $this->isMainRequest = true; + + return $response; + } + + /** + * Restarts the client. + * + * It flushes history and all cookies. + */ + public function restart() + { + $this->cookieJar->clear(); + $this->history->clear(); + } + + /** + * Takes a URI and converts it to absolute if it is not already absolute. + * + * @param string $uri A URI + * + * @return string An absolute URI + */ + protected function getAbsoluteUri($uri) + { + // already absolute? + if (0 === strpos($uri, 'http://') || 0 === strpos($uri, 'https://')) { + return $uri; + } + + if (!$this->history->isEmpty()) { + $currentUri = $this->history->current()->getUri(); + } else { + $currentUri = sprintf('http%s://%s/', + isset($this->server['HTTPS']) ? 's' : '', + isset($this->server['HTTP_HOST']) ? $this->server['HTTP_HOST'] : 'localhost' + ); + } + + // protocol relative URL + if (0 === strpos($uri, '//')) { + return parse_url($currentUri, PHP_URL_SCHEME).':'.$uri; + } + + // anchor or query string parameters? + if (!$uri || '#' == $uri[0] || '?' == $uri[0]) { + return preg_replace('/[#?].*?$/', '', $currentUri).$uri; + } + + if ('/' !== $uri[0]) { + $path = parse_url($currentUri, PHP_URL_PATH); + + if ('/' !== substr($path, -1)) { + $path = substr($path, 0, strrpos($path, '/') + 1); + } + + $uri = $path.$uri; + } + + return preg_replace('#^(.*?//[^/]+)\/.*$#', '$1', $currentUri).$uri; + } + + /** + * Makes a request from a Request object directly. + * + * @param Request $request A Request instance + * @param bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload()) + * + * @return Crawler + */ + protected function requestFromRequest(Request $request, $changeHistory = true) + { + return $this->request($request->getMethod(), $request->getUri(), $request->getParameters(), $request->getFiles(), $request->getServer(), $request->getContent(), $changeHistory); + } + + private function updateServerFromUri($server, $uri) + { + $server['HTTP_HOST'] = $this->extractHost($uri); + $scheme = parse_url($uri, PHP_URL_SCHEME); + $server['HTTPS'] = null === $scheme ? $server['HTTPS'] : 'https' == $scheme; + unset($server['HTTP_IF_NONE_MATCH'], $server['HTTP_IF_MODIFIED_SINCE']); + + return $server; + } + + private function extractHost($uri) + { + $host = parse_url($uri, PHP_URL_HOST); + + if ($port = parse_url($uri, PHP_URL_PORT)) { + return $host.':'.$port; + } + + return $host; + } +} diff --git a/vendor/symfony/browser-kit/Cookie.php b/vendor/symfony/browser-kit/Cookie.php new file mode 100644 index 00000000..42f184d5 --- /dev/null +++ b/vendor/symfony/browser-kit/Cookie.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\BrowserKit; + +/** + * Cookie represents an HTTP cookie. + * + * @author Fabien Potencier + */ +class Cookie +{ + /** + * Handles dates as defined by RFC 2616 section 3.3.1, and also some other + * non-standard, but common formats. + * + * @var array + */ + private static $dateFormats = array( + 'D, d M Y H:i:s T', + 'D, d-M-y H:i:s T', + 'D, d-M-Y H:i:s T', + 'D, d-m-y H:i:s T', + 'D, d-m-Y H:i:s T', + 'D M j G:i:s Y', + 'D M d H:i:s Y T', + ); + + protected $name; + protected $value; + protected $expires; + protected $path; + protected $domain; + protected $secure; + protected $httponly; + protected $rawValue; + + /** + * Sets a cookie. + * + * @param string $name The cookie name + * @param string $value The value of the cookie + * @param string $expires 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 + * @param bool $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client + * @param bool $httponly The cookie httponly flag + * @param bool $encodedValue Whether the value is encoded or not + */ + public function __construct($name, $value, $expires = null, $path = null, $domain = '', $secure = false, $httponly = true, $encodedValue = false) + { + if ($encodedValue) { + $this->value = urldecode($value); + $this->rawValue = $value; + } else { + $this->value = $value; + $this->rawValue = urlencode($value); + } + $this->name = $name; + $this->path = empty($path) ? '/' : $path; + $this->domain = $domain; + $this->secure = (bool) $secure; + $this->httponly = (bool) $httponly; + + if (null !== $expires) { + $timestampAsDateTime = \DateTime::createFromFormat('U', $expires); + if (false === $timestampAsDateTime) { + throw new \UnexpectedValueException(sprintf('The cookie expiration time "%s" is not valid.', $expires)); + } + + $this->expires = $timestampAsDateTime->format('U'); + } + } + + /** + * Returns the HTTP representation of the Cookie. + * + * @return string The HTTP representation of the Cookie + * + * @throws \UnexpectedValueException + */ + public function __toString() + { + $cookie = sprintf('%s=%s', $this->name, $this->rawValue); + + if (null !== $this->expires) { + $dateTime = \DateTime::createFromFormat('U', $this->expires, new \DateTimeZone('GMT')); + $cookie .= '; expires='.str_replace('+0000', '', $dateTime->format(self::$dateFormats[0])); + } + + if ('' !== $this->domain) { + $cookie .= '; domain='.$this->domain; + } + + if ($this->path) { + $cookie .= '; path='.$this->path; + } + + if ($this->secure) { + $cookie .= '; secure'; + } + + if ($this->httponly) { + $cookie .= '; httponly'; + } + + return $cookie; + } + + /** + * Creates a Cookie instance from a Set-Cookie header value. + * + * @param string $cookie A Set-Cookie header value + * @param string $url The base URL + * + * @return static + * + * @throws \InvalidArgumentException + */ + public static function fromString($cookie, $url = null) + { + $parts = explode(';', $cookie); + + if (false === strpos($parts[0], '=')) { + throw new \InvalidArgumentException(sprintf('The cookie string "%s" is not valid.', $parts[0])); + } + + list($name, $value) = explode('=', array_shift($parts), 2); + + $values = array( + 'name' => trim($name), + 'value' => trim($value), + 'expires' => null, + 'path' => '/', + 'domain' => '', + 'secure' => false, + 'httponly' => false, + 'passedRawValue' => true, + ); + + if (null !== $url) { + if ((false === $urlParts = parse_url($url)) || !isset($urlParts['host'])) { + throw new \InvalidArgumentException(sprintf('The URL "%s" is not valid.', $url)); + } + + $values['domain'] = $urlParts['host']; + $values['path'] = isset($urlParts['path']) ? substr($urlParts['path'], 0, strrpos($urlParts['path'], '/')) : ''; + } + + foreach ($parts as $part) { + $part = trim($part); + + if ('secure' === strtolower($part)) { + // Ignore the secure flag if the original URI is not given or is not HTTPS + if (!$url || !isset($urlParts['scheme']) || 'https' != $urlParts['scheme']) { + continue; + } + + $values['secure'] = true; + + continue; + } + + if ('httponly' === strtolower($part)) { + $values['httponly'] = true; + + continue; + } + + if (2 === count($elements = explode('=', $part, 2))) { + if ('expires' === strtolower($elements[0])) { + $elements[1] = self::parseDate($elements[1]); + } + + $values[strtolower($elements[0])] = $elements[1]; + } + } + + return new static( + $values['name'], + $values['value'], + $values['expires'], + $values['path'], + $values['domain'], + $values['secure'], + $values['httponly'], + $values['passedRawValue'] + ); + } + + private static function parseDate($dateValue) + { + // trim single quotes around date if present + if (($length = strlen($dateValue)) > 1 && "'" === $dateValue[0] && "'" === $dateValue[$length - 1]) { + $dateValue = substr($dateValue, 1, -1); + } + + foreach (self::$dateFormats as $dateFormat) { + if (false !== $date = \DateTime::createFromFormat($dateFormat, $dateValue, new \DateTimeZone('GMT'))) { + return $date->format('U'); + } + } + + // attempt a fallback for unusual formatting + if (false !== $date = date_create($dateValue, new \DateTimeZone('GMT'))) { + return $date->format('U'); + } + } + + /** + * Gets the name of the cookie. + * + * @return string The cookie name + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the cookie. + * + * @return string The cookie value + */ + public function getValue() + { + return $this->value; + } + + /** + * Gets the raw value of the cookie. + * + * @return string The cookie value + */ + public function getRawValue() + { + return $this->rawValue; + } + + /** + * Gets the expires time of the cookie. + * + * @return string The cookie expires time + */ + public function getExpiresTime() + { + return $this->expires; + } + + /** + * Gets the path of the cookie. + * + * @return string The cookie path + */ + public function getPath() + { + return $this->path; + } + + /** + * Gets the domain of the cookie. + * + * @return string The cookie domain + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Returns the secure flag of the cookie. + * + * @return bool The cookie secure flag + */ + public function isSecure() + { + return $this->secure; + } + + /** + * Returns the httponly flag of the cookie. + * + * @return bool The cookie httponly flag + */ + public function isHttpOnly() + { + return $this->httponly; + } + + /** + * Returns true if the cookie has expired. + * + * @return bool true if the cookie has expired, false otherwise + */ + public function isExpired() + { + return null !== $this->expires && 0 != $this->expires && $this->expires < time(); + } +} diff --git a/vendor/symfony/browser-kit/CookieJar.php b/vendor/symfony/browser-kit/CookieJar.php new file mode 100644 index 00000000..4b9661b6 --- /dev/null +++ b/vendor/symfony/browser-kit/CookieJar.php @@ -0,0 +1,255 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit; + +/** + * CookieJar. + * + * @author Fabien Potencier + */ +class CookieJar +{ + protected $cookieJar = array(); + + /** + * Sets a cookie. + * + * @param Cookie $cookie A Cookie instance + */ + public function set(Cookie $cookie) + { + $this->cookieJar[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; + } + + /** + * Gets a cookie by name. + * + * You should never use an empty domain, but if you do so, + * this method returns the first cookie for the given name/path + * (this behavior ensures a BC behavior with previous versions of + * Symfony). + * + * @param string $name The cookie name + * @param string $path The cookie path + * @param string $domain The cookie domain + * + * @return Cookie|null A Cookie instance or null if the cookie does not exist + */ + public function get($name, $path = '/', $domain = null) + { + $this->flushExpiredCookies(); + + if (!empty($domain)) { + foreach ($this->cookieJar as $cookieDomain => $pathCookies) { + if ($cookieDomain) { + $cookieDomain = '.'.ltrim($cookieDomain, '.'); + if ($cookieDomain != substr('.'.$domain, -strlen($cookieDomain))) { + continue; + } + } + + foreach ($pathCookies as $cookiePath => $namedCookies) { + if ($cookiePath != substr($path, 0, strlen($cookiePath))) { + continue; + } + if (isset($namedCookies[$name])) { + return $namedCookies[$name]; + } + } + } + + return; + } + + // avoid relying on this behavior that is mainly here for BC reasons + foreach ($this->cookieJar as $cookies) { + if (isset($cookies[$path][$name])) { + return $cookies[$path][$name]; + } + } + } + + /** + * Removes a cookie by name. + * + * You should never use an empty domain, but if you do so, + * all cookies for the given name/path expire (this behavior + * ensures a BC behavior with previous versions of Symfony). + * + * @param string $name The cookie name + * @param string $path The cookie path + * @param string $domain The cookie domain + */ + public function expire($name, $path = '/', $domain = null) + { + if (null === $path) { + $path = '/'; + } + + if (empty($domain)) { + // an empty domain means any domain + // this should never happen but it allows for a better BC + $domains = array_keys($this->cookieJar); + } else { + $domains = array($domain); + } + + foreach ($domains as $domain) { + unset($this->cookieJar[$domain][$path][$name]); + + if (empty($this->cookieJar[$domain][$path])) { + unset($this->cookieJar[$domain][$path]); + + if (empty($this->cookieJar[$domain])) { + unset($this->cookieJar[$domain]); + } + } + } + } + + /** + * Removes all the cookies from the jar. + */ + public function clear() + { + $this->cookieJar = array(); + } + + /** + * Updates the cookie jar from a response Set-Cookie headers. + * + * @param array $setCookies Set-Cookie headers from an HTTP response + * @param string $uri The base URL + */ + public function updateFromSetCookie(array $setCookies, $uri = null) + { + $cookies = array(); + + foreach ($setCookies as $cookie) { + foreach (explode(',', $cookie) as $i => $part) { + if (0 === $i || preg_match('/^(?P\s*[0-9A-Za-z!#\$%\&\'\*\+\-\.^_`\|~]+)=/', $part)) { + $cookies[] = ltrim($part); + } else { + $cookies[count($cookies) - 1] .= ','.$part; + } + } + } + + foreach ($cookies as $cookie) { + try { + $this->set(Cookie::fromString($cookie, $uri)); + } catch (\InvalidArgumentException $e) { + // invalid cookies are just ignored + } + } + } + + /** + * Updates the cookie jar from a Response object. + * + * @param Response $response A Response object + * @param string $uri The base URL + */ + public function updateFromResponse(Response $response, $uri = null) + { + $this->updateFromSetCookie($response->getHeader('Set-Cookie', false), $uri); + } + + /** + * Returns not yet expired cookies. + * + * @return Cookie[] An array of cookies + */ + public function all() + { + $this->flushExpiredCookies(); + + $flattenedCookies = array(); + foreach ($this->cookieJar as $path) { + foreach ($path as $cookies) { + foreach ($cookies as $cookie) { + $flattenedCookies[] = $cookie; + } + } + } + + return $flattenedCookies; + } + + /** + * Returns not yet expired cookie values for the given URI. + * + * @param string $uri A URI + * @param bool $returnsRawValue Returns raw value or urldecoded value + * + * @return array An array of cookie values + */ + public function allValues($uri, $returnsRawValue = false) + { + $this->flushExpiredCookies(); + + $parts = array_replace(array('path' => '/'), parse_url($uri)); + $cookies = array(); + foreach ($this->cookieJar as $domain => $pathCookies) { + if ($domain) { + $domain = '.'.ltrim($domain, '.'); + if ($domain != substr('.'.$parts['host'], -strlen($domain))) { + continue; + } + } + + foreach ($pathCookies as $path => $namedCookies) { + if ($path != substr($parts['path'], 0, strlen($path))) { + continue; + } + + foreach ($namedCookies as $cookie) { + if ($cookie->isSecure() && 'https' != $parts['scheme']) { + continue; + } + + $cookies[$cookie->getName()] = $returnsRawValue ? $cookie->getRawValue() : $cookie->getValue(); + } + } + } + + return $cookies; + } + + /** + * Returns not yet expired raw cookie values for the given URI. + * + * @param string $uri A URI + * + * @return array An array of cookie values + */ + public function allRawValues($uri) + { + return $this->allValues($uri, true); + } + + /** + * Removes all expired cookies. + */ + public function flushExpiredCookies() + { + foreach ($this->cookieJar as $domain => $pathCookies) { + foreach ($pathCookies as $path => $namedCookies) { + foreach ($namedCookies as $name => $cookie) { + if ($cookie->isExpired()) { + unset($this->cookieJar[$domain][$path][$name]); + } + } + } + } + } +} diff --git a/vendor/symfony/browser-kit/History.php b/vendor/symfony/browser-kit/History.php new file mode 100644 index 00000000..8e38fe5c --- /dev/null +++ b/vendor/symfony/browser-kit/History.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\BrowserKit; + +/** + * History. + * + * @author Fabien Potencier + */ +class History +{ + protected $stack = array(); + protected $position = -1; + + /** + * Clears the history. + */ + public function clear() + { + $this->stack = array(); + $this->position = -1; + } + + /** + * Adds a Request to the history. + * + * @param Request $request A Request instance + */ + public function add(Request $request) + { + $this->stack = array_slice($this->stack, 0, $this->position + 1); + $this->stack[] = clone $request; + $this->position = count($this->stack) - 1; + } + + /** + * Returns true if the history is empty. + * + * @return bool true if the history is empty, false otherwise + */ + public function isEmpty() + { + return count($this->stack) == 0; + } + + /** + * Goes back in the history. + * + * @return Request A Request instance + * + * @throws \LogicException if the stack is already on the first page + */ + public function back() + { + if ($this->position < 1) { + throw new \LogicException('You are already on the first page.'); + } + + return clone $this->stack[--$this->position]; + } + + /** + * Goes forward in the history. + * + * @return Request A Request instance + * + * @throws \LogicException if the stack is already on the last page + */ + public function forward() + { + if ($this->position > count($this->stack) - 2) { + throw new \LogicException('You are already on the last page.'); + } + + return clone $this->stack[++$this->position]; + } + + /** + * Returns the current element in the history. + * + * @return Request A Request instance + * + * @throws \LogicException if the stack is empty + */ + public function current() + { + if (-1 == $this->position) { + throw new \LogicException('The page history is empty.'); + } + + return clone $this->stack[$this->position]; + } +} diff --git a/vendor/symfony/browser-kit/LICENSE b/vendor/symfony/browser-kit/LICENSE new file mode 100644 index 00000000..17d16a13 --- /dev/null +++ b/vendor/symfony/browser-kit/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/vendor/symfony/browser-kit/README.md b/vendor/symfony/browser-kit/README.md new file mode 100644 index 00000000..a0083ac5 --- /dev/null +++ b/vendor/symfony/browser-kit/README.md @@ -0,0 +1,14 @@ +BrowserKit Component +==================== + +The BrowserKit component simulates the behavior of a web browser, allowing you +to make requests, click on links and submit forms programmatically. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/browser_kit/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/vendor/symfony/browser-kit/Request.php b/vendor/symfony/browser-kit/Request.php new file mode 100644 index 00000000..2e2819b4 --- /dev/null +++ b/vendor/symfony/browser-kit/Request.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\BrowserKit; + +/** + * Request object. + * + * @author Fabien Potencier + */ +class Request +{ + protected $uri; + protected $method; + protected $parameters; + protected $files; + protected $cookies; + protected $server; + protected $content; + + /** + * Constructor. + * + * @param string $uri The request URI + * @param string $method The HTTP method request + * @param array $parameters The request parameters + * @param array $files An array of uploaded files + * @param array $cookies An array of cookies + * @param array $server An array of server parameters + * @param string $content The raw body data + */ + public function __construct($uri, $method, array $parameters = array(), array $files = array(), array $cookies = array(), array $server = array(), $content = null) + { + $this->uri = $uri; + $this->method = $method; + $this->parameters = $parameters; + $this->files = $files; + $this->cookies = $cookies; + $this->server = $server; + $this->content = $content; + } + + /** + * Gets the request URI. + * + * @return string The request URI + */ + public function getUri() + { + return $this->uri; + } + + /** + * Gets the request HTTP method. + * + * @return string The request HTTP method + */ + public function getMethod() + { + return $this->method; + } + + /** + * Gets the request parameters. + * + * @return array The request parameters + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Gets the request server files. + * + * @return array The request files + */ + public function getFiles() + { + return $this->files; + } + + /** + * Gets the request cookies. + * + * @return array The request cookies + */ + public function getCookies() + { + return $this->cookies; + } + + /** + * Gets the request server parameters. + * + * @return array The request server parameters + */ + public function getServer() + { + return $this->server; + } + + /** + * Gets the request raw body data. + * + * @return string The request raw body data + */ + public function getContent() + { + return $this->content; + } +} diff --git a/vendor/symfony/browser-kit/Response.php b/vendor/symfony/browser-kit/Response.php new file mode 100644 index 00000000..984442fb --- /dev/null +++ b/vendor/symfony/browser-kit/Response.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\BrowserKit; + +/** + * Response object. + * + * @author Fabien Potencier + */ +class Response +{ + protected $content; + protected $status; + protected $headers; + + /** + * Constructor. + * + * The headers array is a set of key/value pairs. If a header is present multiple times + * then the value is an array of all the values. + * + * @param string $content The content of the response + * @param int $status The response status code + * @param array $headers An array of headers + */ + public function __construct($content = '', $status = 200, array $headers = array()) + { + $this->content = $content; + $this->status = $status; + $this->headers = $headers; + } + + /** + * Converts the response object to string containing all headers and the response content. + * + * @return string The response with headers and content + */ + public function __toString() + { + $headers = ''; + foreach ($this->headers as $name => $value) { + if (is_string($value)) { + $headers .= $this->buildHeader($name, $value); + } else { + foreach ($value as $headerValue) { + $headers .= $this->buildHeader($name, $headerValue); + } + } + } + + return $headers."\n".$this->content; + } + + /** + * Returns the build header line. + * + * @param string $name The header name + * @param string $value The header value + * + * @return string The built header line + */ + protected function buildHeader($name, $value) + { + return sprintf("%s: %s\n", $name, $value); + } + + /** + * Gets the response content. + * + * @return string The response content + */ + public function getContent() + { + return $this->content; + } + + /** + * Gets the response status code. + * + * @return int The response status code + */ + public function getStatus() + { + return $this->status; + } + + /** + * Gets the response headers. + * + * @return array The response headers + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Gets a response header. + * + * @param string $header The header name + * @param bool $first Whether to return the first value or all header values + * + * @return string|array The first header value if $first is true, an array of values otherwise + */ + public function getHeader($header, $first = true) + { + $normalizedHeader = str_replace('-', '_', strtolower($header)); + foreach ($this->headers as $key => $value) { + if (str_replace('-', '_', strtolower($key)) === $normalizedHeader) { + if ($first) { + return is_array($value) ? (count($value) ? $value[0] : '') : $value; + } + + return is_array($value) ? $value : array($value); + } + } + + return $first ? null : array(); + } +} diff --git a/vendor/symfony/browser-kit/Tests/ClientTest.php b/vendor/symfony/browser-kit/Tests/ClientTest.php new file mode 100644 index 00000000..5bd4dad7 --- /dev/null +++ b/vendor/symfony/browser-kit/Tests/ClientTest.php @@ -0,0 +1,699 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\BrowserKit\Client; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\BrowserKit\Response; + +class SpecialResponse extends Response +{ +} + +class TestClient extends Client +{ + protected $nextResponse = null; + protected $nextScript = null; + + public function setNextResponse(Response $response) + { + $this->nextResponse = $response; + } + + public function setNextScript($script) + { + $this->nextScript = $script; + } + + protected function doRequest($request) + { + if (null === $this->nextResponse) { + return new Response(); + } + + $response = $this->nextResponse; + $this->nextResponse = null; + + return $response; + } + + protected function filterResponse($response) + { + if ($response instanceof SpecialResponse) { + return new Response($response->getContent(), $response->getStatus(), $response->getHeaders()); + } + + return $response; + } + + protected function getScript($request) + { + $r = new \ReflectionClass('Symfony\Component\BrowserKit\Response'); + $path = $r->getFileName(); + + return <<nextScript); +EOF; + } +} + +class ClientTest extends TestCase +{ + public function testGetHistory() + { + $client = new TestClient(array(), $history = new History()); + $this->assertSame($history, $client->getHistory(), '->getHistory() returns the History'); + } + + public function testGetCookieJar() + { + $client = new TestClient(array(), null, $cookieJar = new CookieJar()); + $this->assertSame($cookieJar, $client->getCookieJar(), '->getCookieJar() returns the CookieJar'); + } + + public function testGetRequest() + { + $client = new TestClient(); + $client->request('GET', 'http://example.com/'); + + $this->assertEquals('http://example.com/', $client->getRequest()->getUri(), '->getCrawler() returns the Request of the last request'); + } + + public function testGetRequestWithIpAsHttpHost() + { + $client = new TestClient(); + $client->request('GET', 'https://example.com/foo', array(), array(), array('HTTP_HOST' => '127.0.0.1')); + + $this->assertEquals('https://example.com/foo', $client->getRequest()->getUri()); + $headers = $client->getRequest()->getServer(); + $this->assertEquals('127.0.0.1', $headers['HTTP_HOST']); + } + + public function testGetResponse() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo')); + $client->request('GET', 'http://example.com/'); + + $this->assertEquals('foo', $client->getResponse()->getContent(), '->getCrawler() returns the Response of the last request'); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Response', $client->getResponse(), '->getCrawler() returns the Response of the last request'); + } + + public function testGetInternalResponse() + { + $client = new TestClient(); + $client->setNextResponse(new SpecialResponse('foo')); + $client->request('GET', 'http://example.com/'); + + $this->assertInstanceOf('Symfony\Component\BrowserKit\Response', $client->getInternalResponse()); + $this->assertNotInstanceOf('Symfony\Component\BrowserKit\Tests\SpecialResponse', $client->getInternalResponse()); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Tests\SpecialResponse', $client->getResponse()); + } + + public function testGetContent() + { + $json = '{"jsonrpc":"2.0","method":"echo","id":7,"params":["Hello World"]}'; + + $client = new TestClient(); + $client->request('POST', 'http://example.com/jsonrpc', array(), array(), array(), $json); + $this->assertEquals($json, $client->getRequest()->getContent()); + } + + public function testGetCrawler() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo')); + $crawler = $client->request('GET', 'http://example.com/'); + + $this->assertSame($crawler, $client->getCrawler(), '->getCrawler() returns the Crawler of the last request'); + } + + public function testRequestHttpHeaders() + { + $client = new TestClient(); + $client->request('GET', '/'); + $headers = $client->getRequest()->getServer(); + $this->assertEquals('localhost', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com'); + $headers = $client->getRequest()->getServer(); + $this->assertEquals('www.example.com', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header'); + + $client->request('GET', 'https://www.example.com'); + $headers = $client->getRequest()->getServer(); + $this->assertTrue($headers['HTTPS'], '->request() sets the HTTPS header'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com:8080'); + $headers = $client->getRequest()->getServer(); + $this->assertEquals('www.example.com:8080', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header with port'); + } + + public function testRequestURIConversion() + { + $client = new TestClient(); + $client->request('GET', '/foo'); + $this->assertEquals('http://localhost/foo', $client->getRequest()->getUri(), '->request() converts the URI to an absolute one'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com'); + $this->assertEquals('http://www.example.com', $client->getRequest()->getUri(), '->request() does not change absolute URIs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/'); + $client->request('GET', '/foo'); + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo'); + $client->request('GET', '#'); + $this->assertEquals('http://www.example.com/foo#', $client->getRequest()->getUri(), '->request() uses the previous request for #'); + $client->request('GET', '#'); + $this->assertEquals('http://www.example.com/foo#', $client->getRequest()->getUri(), '->request() uses the previous request for #'); + $client->request('GET', '#foo'); + $this->assertEquals('http://www.example.com/foo#foo', $client->getRequest()->getUri(), '->request() uses the previous request for #'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/'); + $client->request('GET', 'bar'); + $this->assertEquals('http://www.example.com/foo/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'bar'); + $this->assertEquals('http://www.example.com/foo/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/'); + $client->request('GET', 'http'); + $this->assertEquals('http://www.example.com/foo/http', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo'); + $client->request('GET', 'http/bar'); + $this->assertEquals('http://www.example.com/http/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/'); + $client->request('GET', 'http'); + $this->assertEquals('http://www.example.com/http', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo'); + $client->request('GET', '?'); + $this->assertEquals('http://www.example.com/foo?', $client->getRequest()->getUri(), '->request() uses the previous request for ?'); + $client->request('GET', '?'); + $this->assertEquals('http://www.example.com/foo?', $client->getRequest()->getUri(), '->request() uses the previous request for ?'); + $client->request('GET', '?foo=bar'); + $this->assertEquals('http://www.example.com/foo?foo=bar', $client->getRequest()->getUri(), '->request() uses the previous request for ?'); + } + + public function testRequestReferer() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'bar'); + $server = $client->getRequest()->getServer(); + $this->assertEquals('http://www.example.com/foo/foobar', $server['HTTP_REFERER'], '->request() sets the referer'); + } + + public function testRequestHistory() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'bar'); + + $this->assertEquals('http://www.example.com/foo/bar', $client->getHistory()->current()->getUri(), '->request() updates the History'); + $this->assertEquals('http://www.example.com/foo/foobar', $client->getHistory()->back()->getUri(), '->request() updates the History'); + } + + public function testRequestCookies() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo', 200, array('Set-Cookie' => 'foo=bar'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->assertEquals(array('foo' => 'bar'), $client->getCookieJar()->allValues('http://www.example.com/foo/foobar'), '->request() updates the CookieJar'); + + $client->request('GET', 'bar'); + $this->assertEquals(array('foo' => 'bar'), $client->getCookieJar()->allValues('http://www.example.com/foo/foobar'), '->request() updates the CookieJar'); + } + + public function testRequestSecureCookies() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo', 200, array('Set-Cookie' => 'foo=bar; path=/; secure'))); + $client->request('GET', 'https://www.example.com/foo/foobar'); + + $this->assertTrue($client->getCookieJar()->get('foo', '/', 'www.example.com')->isSecure()); + } + + public function testClick() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo')); + $crawler = $client->request('GET', 'http://www.example.com/foo/foobar'); + + $client->click($crawler->filter('a')->link()); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->click() clicks on links'); + } + + public function testClickForm() + { + $client = new TestClient(); + $client->setNextResponse(new Response('
')); + $crawler = $client->request('GET', 'http://www.example.com/foo/foobar'); + + $client->click($crawler->filter('input')->form()); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->click() Form submit forms'); + } + + public function testSubmit() + { + $client = new TestClient(); + $client->setNextResponse(new Response('
')); + $crawler = $client->request('GET', 'http://www.example.com/foo/foobar'); + + $client->submit($crawler->filter('input')->form()); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->submit() submit forms'); + } + + public function testSubmitPreserveAuth() + { + $client = new TestClient(array('PHP_AUTH_USER' => 'foo', 'PHP_AUTH_PW' => 'bar')); + $client->setNextResponse(new Response('
')); + $crawler = $client->request('GET', 'http://www.example.com/foo/foobar'); + + $server = $client->getRequest()->getServer(); + $this->assertArrayHasKey('PHP_AUTH_USER', $server); + $this->assertEquals('foo', $server['PHP_AUTH_USER']); + $this->assertArrayHasKey('PHP_AUTH_PW', $server); + $this->assertEquals('bar', $server['PHP_AUTH_PW']); + + $client->submit($crawler->filter('input')->form()); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->submit() submit forms'); + + $server = $client->getRequest()->getServer(); + $this->assertArrayHasKey('PHP_AUTH_USER', $server); + $this->assertEquals('foo', $server['PHP_AUTH_USER']); + $this->assertArrayHasKey('PHP_AUTH_PW', $server); + $this->assertEquals('bar', $server['PHP_AUTH_PW']); + } + + public function testFollowRedirect() + { + $client = new TestClient(); + $client->followRedirects(false); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + try { + $client->followRedirect(); + $this->fail('->followRedirect() throws a \LogicException if the request was not redirected'); + } catch (\Exception $e) { + $this->assertInstanceOf('LogicException', $e, '->followRedirect() throws a \LogicException if the request was not redirected'); + } + + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->followRedirect(); + + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any'); + + $client = new TestClient(); + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() automatically follows redirects if followRedirects is true'); + + $client = new TestClient(); + $client->setNextResponse(new Response('', 201, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + $this->assertEquals('http://www.example.com/foo/foobar', $client->getRequest()->getUri(), '->followRedirect() does not follow redirect if HTTP Code is not 30x'); + + $client = new TestClient(); + $client->setNextResponse(new Response('', 201, array('Location' => 'http://www.example.com/redirected'))); + $client->followRedirects(false); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + try { + $client->followRedirect(); + $this->fail('->followRedirect() throws a \LogicException if the request did not respond with 30x HTTP Code'); + } catch (\Exception $e) { + $this->assertInstanceOf('LogicException', $e, '->followRedirect() throws a \LogicException if the request did not respond with 30x HTTP Code'); + } + } + + public function testFollowRelativeRedirect() + { + $client = new TestClient(); + $client->setNextResponse(new Response('', 302, array('Location' => '/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any'); + + $client = new TestClient(); + $client->setNextResponse(new Response('', 302, array('Location' => '/redirected:1234'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->assertEquals('http://www.example.com/redirected:1234', $client->getRequest()->getUri(), '->followRedirect() follows relative urls'); + } + + public function testFollowRedirectWithMaxRedirects() + { + $client = new TestClient(); + $client->setMaxRedirects(1); + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any'); + + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected2'))); + try { + $client->followRedirect(); + $this->fail('->followRedirect() throws a \LogicException if the request was redirected and limit of redirections was reached'); + } catch (\Exception $e) { + $this->assertInstanceOf('LogicException', $e, '->followRedirect() throws a \LogicException if the request was redirected and limit of redirections was reached'); + } + + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any'); + + $client->setNextResponse(new Response('', 302, array('Location' => '/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows relative URLs'); + + $client = new TestClient(); + $client->setNextResponse(new Response('', 302, array('Location' => '//www.example.org/'))); + $client->request('GET', 'https://www.example.com/'); + + $this->assertEquals('https://www.example.org/', $client->getRequest()->getUri(), '->followRedirect() follows protocol-relative URLs'); + + $client = new TestClient(); + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected'))); + $client->request('POST', 'http://www.example.com/foo/foobar', array('name' => 'bar')); + + $this->assertEquals('GET', $client->getRequest()->getMethod(), '->followRedirect() uses a GET for 302'); + $this->assertEquals(array(), $client->getRequest()->getParameters(), '->followRedirect() does not submit parameters when changing the method'); + } + + public function testFollowRedirectWithCookies() + { + $client = new TestClient(); + $client->followRedirects(false); + $client->setNextResponse(new Response('', 302, array( + 'Location' => 'http://www.example.com/redirected', + 'Set-Cookie' => 'foo=bar', + ))); + $client->request('GET', 'http://www.example.com/'); + $this->assertEquals(array(), $client->getRequest()->getCookies()); + $client->followRedirect(); + $this->assertEquals(array('foo' => 'bar'), $client->getRequest()->getCookies()); + } + + public function testFollowRedirectWithHeaders() + { + $headers = array( + 'HTTP_HOST' => 'www.example.com', + 'HTTP_USER_AGENT' => 'Symfony BrowserKit', + 'CONTENT_TYPE' => 'application/vnd.custom+xml', + 'HTTPS' => false, + ); + + $client = new TestClient(); + $client->followRedirects(false); + $client->setNextResponse(new Response('', 302, array( + 'Location' => 'http://www.example.com/redirected', + ))); + $client->request('GET', 'http://www.example.com/', array(), array(), array( + 'CONTENT_TYPE' => 'application/vnd.custom+xml', + )); + + $this->assertEquals($headers, $client->getRequest()->getServer()); + + $client->followRedirect(); + + $headers['HTTP_REFERER'] = 'http://www.example.com/'; + + $this->assertEquals($headers, $client->getRequest()->getServer()); + } + + public function testFollowRedirectWithPort() + { + $headers = array( + 'HTTP_HOST' => 'www.example.com:8080', + 'HTTP_USER_AGENT' => 'Symfony BrowserKit', + 'HTTPS' => false, + 'HTTP_REFERER' => 'http://www.example.com:8080/', + ); + + $client = new TestClient(); + $client->setNextResponse(new Response('', 302, array( + 'Location' => 'http://www.example.com:8080/redirected', + ))); + $client->request('GET', 'http://www.example.com:8080/'); + + $this->assertEquals($headers, $client->getRequest()->getServer()); + } + + public function testIsFollowingRedirects() + { + $client = new TestClient(); + $this->assertTrue($client->isFollowingRedirects(), '->getFollowRedirects() returns default value'); + $client->followRedirects(false); + $this->assertFalse($client->isFollowingRedirects(), '->getFollowRedirects() returns assigned value'); + } + + public function testGetMaxRedirects() + { + $client = new TestClient(); + $this->assertEquals(-1, $client->getMaxRedirects(), '->getMaxRedirects() returns default value'); + $client->setMaxRedirects(3); + $this->assertEquals(3, $client->getMaxRedirects(), '->getMaxRedirects() returns assigned value'); + } + + public function testFollowRedirectWithPostMethod() + { + $parameters = array('foo' => 'bar'); + $files = array('myfile.foo' => 'baz'); + $server = array('X_TEST_FOO' => 'bazbar'); + $content = 'foobarbaz'; + + $client = new TestClient(); + + $client->setNextResponse(new Response('', 307, array('Location' => 'http://www.example.com/redirected'))); + $client->request('POST', 'http://www.example.com/foo/foobar', $parameters, $files, $server, $content); + + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect with POST method'); + $this->assertArrayHasKey('foo', $client->getRequest()->getParameters(), '->followRedirect() keeps parameters with POST method'); + $this->assertArrayHasKey('myfile.foo', $client->getRequest()->getFiles(), '->followRedirect() keeps files with POST method'); + $this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->followRedirect() keeps $_SERVER with POST method'); + $this->assertEquals($content, $client->getRequest()->getContent(), '->followRedirect() keeps content with POST method'); + $this->assertEquals('POST', $client->getRequest()->getMethod(), '->followRedirect() keeps request method'); + } + + public function testFollowRedirectDropPostMethod() + { + $parameters = array('foo' => 'bar'); + $files = array('myfile.foo' => 'baz'); + $server = array('X_TEST_FOO' => 'bazbar'); + $content = 'foobarbaz'; + + $client = new TestClient(); + + foreach (array(301, 302, 303) as $code) { + $client->setNextResponse(new Response('', $code, array('Location' => 'http://www.example.com/redirected'))); + $client->request('POST', 'http://www.example.com/foo/foobar', $parameters, $files, $server, $content); + + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect with POST method on response code: '.$code.'.'); + $this->assertEmpty($client->getRequest()->getParameters(), '->followRedirect() drops parameters with POST method on response code: '.$code.'.'); + $this->assertEmpty($client->getRequest()->getFiles(), '->followRedirect() drops files with POST method on response code: '.$code.'.'); + $this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->followRedirect() keeps $_SERVER with POST method on response code: '.$code.'.'); + $this->assertEmpty($client->getRequest()->getContent(), '->followRedirect() drops content with POST method on response code: '.$code.'.'); + $this->assertEquals('GET', $client->getRequest()->getMethod(), '->followRedirect() drops request method to GET on response code: '.$code.'.'); + } + } + + public function testBack() + { + $client = new TestClient(); + + $parameters = array('foo' => 'bar'); + $files = array('myfile.foo' => 'baz'); + $server = array('X_TEST_FOO' => 'bazbar'); + $content = 'foobarbaz'; + + $client->request('GET', 'http://www.example.com/foo/foobar', $parameters, $files, $server, $content); + $client->request('GET', 'http://www.example.com/foo'); + $client->back(); + + $this->assertEquals('http://www.example.com/foo/foobar', $client->getRequest()->getUri(), '->back() goes back in the history'); + $this->assertArrayHasKey('foo', $client->getRequest()->getParameters(), '->back() keeps parameters'); + $this->assertArrayHasKey('myfile.foo', $client->getRequest()->getFiles(), '->back() keeps files'); + $this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->back() keeps $_SERVER'); + $this->assertEquals($content, $client->getRequest()->getContent(), '->back() keeps content'); + } + + public function testForward() + { + $client = new TestClient(); + + $parameters = array('foo' => 'bar'); + $files = array('myfile.foo' => 'baz'); + $server = array('X_TEST_FOO' => 'bazbar'); + $content = 'foobarbaz'; + + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'http://www.example.com/foo', $parameters, $files, $server, $content); + $client->back(); + $client->forward(); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->forward() goes forward in the history'); + $this->assertArrayHasKey('foo', $client->getRequest()->getParameters(), '->forward() keeps parameters'); + $this->assertArrayHasKey('myfile.foo', $client->getRequest()->getFiles(), '->forward() keeps files'); + $this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->forward() keeps $_SERVER'); + $this->assertEquals($content, $client->getRequest()->getContent(), '->forward() keeps content'); + } + + public function testReload() + { + $client = new TestClient(); + + $parameters = array('foo' => 'bar'); + $files = array('myfile.foo' => 'baz'); + $server = array('X_TEST_FOO' => 'bazbar'); + $content = 'foobarbaz'; + + $client->request('GET', 'http://www.example.com/foo/foobar', $parameters, $files, $server, $content); + $client->reload(); + + $this->assertEquals('http://www.example.com/foo/foobar', $client->getRequest()->getUri(), '->reload() reloads the current page'); + $this->assertArrayHasKey('foo', $client->getRequest()->getParameters(), '->reload() keeps parameters'); + $this->assertArrayHasKey('myfile.foo', $client->getRequest()->getFiles(), '->reload() keeps files'); + $this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->reload() keeps $_SERVER'); + $this->assertEquals($content, $client->getRequest()->getContent(), '->reload() keeps content'); + } + + public function testRestart() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->restart(); + + $this->assertTrue($client->getHistory()->isEmpty(), '->restart() clears the history'); + $this->assertEquals(array(), $client->getCookieJar()->all(), '->restart() clears the cookies'); + } + + public function testInsulatedRequests() + { + $client = new TestClient(); + $client->insulate(); + $client->setNextScript("new Symfony\Component\BrowserKit\Response('foobar')"); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + $this->assertEquals('foobar', $client->getResponse()->getContent(), '->insulate() process the request in a forked process'); + + $client->setNextScript("new Symfony\Component\BrowserKit\Response('foobar)"); + + try { + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->fail('->request() throws a \RuntimeException if the script has an error'); + } catch (\Exception $e) { + $this->assertInstanceOf('RuntimeException', $e, '->request() throws a \RuntimeException if the script has an error'); + } + } + + public function testGetServerParameter() + { + $client = new TestClient(); + $this->assertEquals('', $client->getServerParameter('HTTP_HOST')); + $this->assertEquals('Symfony BrowserKit', $client->getServerParameter('HTTP_USER_AGENT')); + $this->assertEquals('testvalue', $client->getServerParameter('testkey', 'testvalue')); + } + + public function testSetServerParameter() + { + $client = new TestClient(); + + $this->assertEquals('', $client->getServerParameter('HTTP_HOST')); + $this->assertEquals('Symfony BrowserKit', $client->getServerParameter('HTTP_USER_AGENT')); + + $client->setServerParameter('HTTP_HOST', 'testhost'); + $this->assertEquals('testhost', $client->getServerParameter('HTTP_HOST')); + + $client->setServerParameter('HTTP_USER_AGENT', 'testua'); + $this->assertEquals('testua', $client->getServerParameter('HTTP_USER_AGENT')); + } + + public function testSetServerParameterInRequest() + { + $client = new TestClient(); + + $this->assertEquals('', $client->getServerParameter('HTTP_HOST')); + $this->assertEquals('Symfony BrowserKit', $client->getServerParameter('HTTP_USER_AGENT')); + + $client->request('GET', 'https://www.example.com/https/www.example.com', array(), array(), array( + 'HTTP_HOST' => 'testhost', + 'HTTP_USER_AGENT' => 'testua', + 'HTTPS' => false, + 'NEW_SERVER_KEY' => 'new-server-key-value', + )); + + $this->assertEquals('', $client->getServerParameter('HTTP_HOST')); + $this->assertEquals('Symfony BrowserKit', $client->getServerParameter('HTTP_USER_AGENT')); + + $this->assertEquals('http://www.example.com/https/www.example.com', $client->getRequest()->getUri()); + + $server = $client->getRequest()->getServer(); + + $this->assertArrayHasKey('HTTP_USER_AGENT', $server); + $this->assertEquals('testua', $server['HTTP_USER_AGENT']); + + $this->assertArrayHasKey('HTTP_HOST', $server); + $this->assertEquals('testhost', $server['HTTP_HOST']); + + $this->assertArrayHasKey('NEW_SERVER_KEY', $server); + $this->assertEquals('new-server-key-value', $server['NEW_SERVER_KEY']); + + $this->assertArrayHasKey('HTTPS', $server); + $this->assertFalse($server['HTTPS']); + } + + public function testInternalRequest() + { + $client = new TestClient(); + + $client->request('GET', 'https://www.example.com/https/www.example.com', array(), array(), array( + 'HTTP_HOST' => 'testhost', + 'HTTP_USER_AGENT' => 'testua', + 'HTTPS' => false, + 'NEW_SERVER_KEY' => 'new-server-key-value', + )); + + $this->assertInstanceOf('Symfony\Component\BrowserKit\Request', $client->getInternalRequest()); + } + + public function testInternalRequestNull() + { + $client = new TestClient(); + $this->assertNull($client->getInternalRequest()); + } +} diff --git a/vendor/symfony/browser-kit/Tests/CookieJarTest.php b/vendor/symfony/browser-kit/Tests/CookieJarTest.php new file mode 100644 index 00000000..ae9f6a7a --- /dev/null +++ b/vendor/symfony/browser-kit/Tests/CookieJarTest.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\BrowserKit\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\BrowserKit\Cookie; +use Symfony\Component\BrowserKit\Response; + +class CookieJarTest extends TestCase +{ + public function testSetGet() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie = new Cookie('foo', 'bar')); + + $this->assertEquals($cookie, $cookieJar->get('foo'), '->set() sets a cookie'); + + $this->assertNull($cookieJar->get('foobar'), '->get() returns null if the cookie does not exist'); + + $cookieJar->set($cookie = new Cookie('foo', 'bar', time() - 86400)); + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + } + + public function testExpire() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie = new Cookie('foo', 'bar')); + $cookieJar->expire('foo'); + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + } + + public function testAll() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar')); + $cookieJar->set($cookie2 = new Cookie('bar', 'foo')); + + $this->assertEquals(array($cookie1, $cookie2), $cookieJar->all(), '->all() returns all cookies in the jar'); + } + + public function testClear() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar')); + $cookieJar->set($cookie2 = new Cookie('bar', 'foo')); + + $cookieJar->clear(); + + $this->assertEquals(array(), $cookieJar->all(), '->clear() expires all cookies'); + } + + public function testUpdateFromResponse() + { + $response = new Response('', 200, array('Set-Cookie' => 'foo=foo')); + + $cookieJar = new CookieJar(); + $cookieJar->updateFromResponse($response); + + $this->assertEquals('foo', $cookieJar->get('foo')->getValue(), '->updateFromResponse() updates cookies from a Response objects'); + } + + public function testUpdateFromSetCookie() + { + $setCookies = array('foo=foo'); + + $cookieJar = new CookieJar(); + $cookieJar->set(new Cookie('bar', 'bar')); + $cookieJar->updateFromSetCookie($setCookies); + + $this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $cookieJar->get('foo')); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $cookieJar->get('bar')); + $this->assertEquals('foo', $cookieJar->get('foo')->getValue(), '->updateFromSetCookie() updates cookies from a Set-Cookie header'); + $this->assertEquals('bar', $cookieJar->get('bar')->getValue(), '->updateFromSetCookie() keeps existing cookies'); + } + + public function testUpdateFromEmptySetCookie() + { + $cookieJar = new CookieJar(); + $cookieJar->updateFromSetCookie(array('')); + $this->assertEquals(array(), $cookieJar->all()); + } + + public function testUpdateFromSetCookieWithMultipleCookies() + { + $timestamp = time() + 3600; + $date = gmdate('D, d M Y H:i:s \G\M\T', $timestamp); + $setCookies = array(sprintf('foo=foo; expires=%s; domain=.symfony.com; path=/, bar=bar; domain=.blog.symfony.com, PHPSESSID=id; expires=%s', $date, $date)); + + $cookieJar = new CookieJar(); + $cookieJar->updateFromSetCookie($setCookies); + + $fooCookie = $cookieJar->get('foo', '/', '.symfony.com'); + $barCookie = $cookieJar->get('bar', '/', '.blog.symfony.com'); + $phpCookie = $cookieJar->get('PHPSESSID'); + + $this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $fooCookie); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $barCookie); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $phpCookie); + $this->assertEquals('foo', $fooCookie->getValue()); + $this->assertEquals('bar', $barCookie->getValue()); + $this->assertEquals('id', $phpCookie->getValue()); + $this->assertEquals($timestamp, $fooCookie->getExpiresTime()); + $this->assertNull($barCookie->getExpiresTime()); + $this->assertEquals($timestamp, $phpCookie->getExpiresTime()); + } + + /** + * @dataProvider provideAllValuesValues + */ + public function testAllValues($uri, $values) + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo_nothing', 'foo')); + $cookieJar->set($cookie2 = new Cookie('foo_expired', 'foo', time() - 86400)); + $cookieJar->set($cookie3 = new Cookie('foo_path', 'foo', null, '/foo')); + $cookieJar->set($cookie4 = new Cookie('foo_domain', 'foo', null, '/', '.example.com')); + $cookieJar->set($cookie4 = new Cookie('foo_strict_domain', 'foo', null, '/', '.www4.example.com')); + $cookieJar->set($cookie5 = new Cookie('foo_secure', 'foo', null, '/', '', true)); + + $this->assertEquals($values, array_keys($cookieJar->allValues($uri)), '->allValues() returns the cookie for a given URI'); + } + + public function provideAllValuesValues() + { + return array( + array('http://www.example.com', array('foo_nothing', 'foo_domain')), + array('http://www.example.com/', array('foo_nothing', 'foo_domain')), + array('http://foo.example.com/', array('foo_nothing', 'foo_domain')), + array('http://foo.example1.com/', array('foo_nothing')), + array('https://foo.example.com/', array('foo_nothing', 'foo_secure', 'foo_domain')), + array('http://www.example.com/foo/bar', array('foo_nothing', 'foo_path', 'foo_domain')), + array('http://www4.example.com/', array('foo_nothing', 'foo_domain', 'foo_strict_domain')), + ); + } + + public function testEncodedValues() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie = new Cookie('foo', 'bar%3Dbaz', null, '/', '', false, true, true)); + + $this->assertEquals(array('foo' => 'bar=baz'), $cookieJar->allValues('/')); + $this->assertEquals(array('foo' => 'bar%3Dbaz'), $cookieJar->allRawValues('/')); + } + + public function testCookieExpireWithSameNameButDifferentPaths() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/foo')); + $cookieJar->set($cookie2 = new Cookie('foo', 'bar2', null, '/bar')); + $cookieJar->expire('foo', '/foo'); + + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + $this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/'))); + $this->assertEquals(array(), $cookieJar->allValues('http://example.com/foo')); + $this->assertEquals(array('foo' => 'bar2'), $cookieJar->allValues('http://example.com/bar')); + } + + public function testCookieExpireWithNullPaths() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/')); + $cookieJar->expire('foo', null); + + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + $this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/'))); + } + + public function testCookieExpireWithDomain() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/foo', 'http://example2.com/')); + $cookieJar->expire('foo', '/foo', 'http://example2.com/'); + + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + $this->assertEquals(array(), array_keys($cookieJar->allValues('http://example2.com/'))); + } + + public function testCookieWithSameNameButDifferentPaths() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/foo')); + $cookieJar->set($cookie2 = new Cookie('foo', 'bar2', null, '/bar')); + + $this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/'))); + $this->assertEquals(array('foo' => 'bar1'), $cookieJar->allValues('http://example.com/foo')); + $this->assertEquals(array('foo' => 'bar2'), $cookieJar->allValues('http://example.com/bar')); + } + + public function testCookieWithSameNameButDifferentDomains() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/', 'foo.example.com')); + $cookieJar->set($cookie2 = new Cookie('foo', 'bar2', null, '/', 'bar.example.com')); + + $this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/'))); + $this->assertEquals(array('foo' => 'bar1'), $cookieJar->allValues('http://foo.example.com/')); + $this->assertEquals(array('foo' => 'bar2'), $cookieJar->allValues('http://bar.example.com/')); + } + + public function testCookieGetWithSubdomain() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar', null, '/', '.example.com')); + $cookieJar->set($cookie2 = new Cookie('foo1', 'bar', null, '/', 'test.example.com')); + + $this->assertEquals($cookie1, $cookieJar->get('foo', '/', 'foo.example.com')); + $this->assertEquals($cookie1, $cookieJar->get('foo', '/', 'example.com')); + $this->assertEquals($cookie2, $cookieJar->get('foo1', '/', 'test.example.com')); + } + + public function testCookieGetWithWrongSubdomain() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo1', 'bar', null, '/', 'test.example.com')); + + $this->assertNull($cookieJar->get('foo1', '/', 'foo.example.com')); + } + + public function testCookieGetWithSubdirectory() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar', null, '/test', '.example.com')); + $cookieJar->set($cookie2 = new Cookie('foo1', 'bar1', null, '/', '.example.com')); + + $this->assertNull($cookieJar->get('foo', '/', '.example.com')); + $this->assertNull($cookieJar->get('foo', '/bar', '.example.com')); + $this->assertEquals($cookie1, $cookieJar->get('foo', '/test', 'example.com')); + $this->assertEquals($cookie2, $cookieJar->get('foo1', '/', 'example.com')); + $this->assertEquals($cookie2, $cookieJar->get('foo1', '/bar', 'example.com')); + } + + public function testCookieWithWildcardDomain() + { + $cookieJar = new CookieJar(); + $cookieJar->set(new Cookie('foo', 'bar', null, '/', '.example.com')); + + $this->assertEquals(array('foo' => 'bar'), $cookieJar->allValues('http://www.example.com')); + $this->assertEmpty($cookieJar->allValues('http://wwwexample.com')); + } +} diff --git a/vendor/symfony/browser-kit/Tests/CookieTest.php b/vendor/symfony/browser-kit/Tests/CookieTest.php new file mode 100644 index 00000000..38ea8122 --- /dev/null +++ b/vendor/symfony/browser-kit/Tests/CookieTest.php @@ -0,0 +1,190 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\BrowserKit\Cookie; + +class CookieTest extends TestCase +{ + /** + * @dataProvider getTestsForToFromString + */ + public function testToFromString($cookie, $url = null) + { + $this->assertEquals($cookie, (string) Cookie::fromString($cookie, $url)); + } + + public function getTestsForToFromString() + { + return array( + array('foo=bar; path=/'), + array('foo=bar; path=/foo'), + array('foo=bar; domain=google.com; path=/'), + array('foo=bar; domain=example.com; path=/; secure', 'https://example.com/'), + array('foo=bar; path=/; httponly'), + array('foo=bar; domain=google.com; path=/foo; secure; httponly', 'https://google.com/'), + array('foo=bar=baz; path=/'), + array('foo=bar%3Dbaz; path=/'), + ); + } + + public function testFromStringIgnoreSecureFlag() + { + $this->assertFalse(Cookie::fromString('foo=bar; secure')->isSecure()); + $this->assertFalse(Cookie::fromString('foo=bar; secure', 'http://example.com/')->isSecure()); + } + + /** + * @dataProvider getExpireCookieStrings + */ + public function testFromStringAcceptsSeveralExpiresDateFormats($cookie) + { + $this->assertEquals(1596185377, Cookie::fromString($cookie)->getExpiresTime()); + } + + public function getExpireCookieStrings() + { + return array( + array('foo=bar; expires=Fri, 31-Jul-2020 08:49:37 GMT'), + array('foo=bar; expires=Fri, 31 Jul 2020 08:49:37 GMT'), + array('foo=bar; expires=Fri, 31-07-2020 08:49:37 GMT'), + array('foo=bar; expires=Fri, 31-07-20 08:49:37 GMT'), + array('foo=bar; expires=Friday, 31-Jul-20 08:49:37 GMT'), + array('foo=bar; expires=Fri Jul 31 08:49:37 2020'), + array('foo=bar; expires=\'Fri Jul 31 08:49:37 2020\''), + array('foo=bar; expires=Friday July 31st 2020, 08:49:37 GMT'), + ); + } + + public function testFromStringWithCapitalization() + { + $this->assertEquals('Foo=Bar; path=/', (string) Cookie::fromString('Foo=Bar')); + $this->assertEquals('foo=bar; expires=Fri, 31 Dec 2010 23:59:59 GMT; path=/', (string) Cookie::fromString('foo=bar; Expires=Fri, 31 Dec 2010 23:59:59 GMT')); + $this->assertEquals('foo=bar; domain=www.example.org; path=/; httponly', (string) Cookie::fromString('foo=bar; DOMAIN=www.example.org; HttpOnly')); + } + + public function testFromStringWithUrl() + { + $this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::fromString('foo=bar', 'http://www.example.com/')); + $this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::fromString('foo=bar', 'http://www.example.com')); + $this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::fromString('foo=bar', 'http://www.example.com?foo')); + $this->assertEquals('foo=bar; domain=www.example.com; path=/foo', (string) Cookie::fromString('foo=bar', 'http://www.example.com/foo/bar')); + $this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::fromString('foo=bar; path=/', 'http://www.example.com/foo/bar')); + $this->assertEquals('foo=bar; domain=www.myotherexample.com; path=/', (string) Cookie::fromString('foo=bar; domain=www.myotherexample.com', 'http://www.example.com/')); + } + + public function testFromStringThrowsAnExceptionIfCookieIsNotValid() + { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException'); + Cookie::fromString('foo'); + } + + public function testFromStringIgnoresInvalidExpiresDate() + { + $cookie = Cookie::fromString('foo=bar; expires=Flursday July 31st 2020, 08:49:37 GMT'); + + $this->assertFalse($cookie->isExpired()); + } + + public function testFromStringThrowsAnExceptionIfUrlIsNotValid() + { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException'); + Cookie::fromString('foo=bar', 'foobar'); + } + + public function testGetName() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertEquals('foo', $cookie->getName(), '->getName() returns the cookie name'); + } + + public function testGetValue() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertEquals('bar', $cookie->getValue(), '->getValue() returns the cookie value'); + + $cookie = new Cookie('foo', 'bar%3Dbaz', null, '/', '', false, true, true); // raw value + $this->assertEquals('bar=baz', $cookie->getValue(), '->getValue() returns the urldecoded cookie value'); + } + + public function testGetRawValue() + { + $cookie = new Cookie('foo', 'bar=baz'); // decoded value + $this->assertEquals('bar%3Dbaz', $cookie->getRawValue(), '->getRawValue() returns the urlencoded cookie value'); + $cookie = new Cookie('foo', 'bar%3Dbaz', null, '/', '', false, true, true); // raw value + $this->assertEquals('bar%3Dbaz', $cookie->getRawValue(), '->getRawValue() returns the non-urldecoded cookie value'); + } + + public function testGetPath() + { + $cookie = new Cookie('foo', 'bar', 0); + $this->assertEquals('/', $cookie->getPath(), '->getPath() returns / is no path is defined'); + + $cookie = new Cookie('foo', 'bar', 0, '/foo'); + $this->assertEquals('/foo', $cookie->getPath(), '->getPath() returns the cookie path'); + } + + public function testGetDomain() + { + $cookie = new Cookie('foo', 'bar', 0, '/', 'foo.com'); + $this->assertEquals('foo.com', $cookie->getDomain(), '->getDomain() returns the cookie domain'); + } + + public function testIsSecure() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertFalse($cookie->isSecure(), '->isSecure() returns false if not defined'); + + $cookie = new Cookie('foo', 'bar', 0, '/', 'foo.com', true); + $this->assertTrue($cookie->isSecure(), '->isSecure() returns the cookie secure flag'); + } + + public function testIsHttponly() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertTrue($cookie->isHttpOnly(), '->isHttpOnly() returns false if not defined'); + + $cookie = new Cookie('foo', 'bar', 0, '/', 'foo.com', false, true); + $this->assertTrue($cookie->isHttpOnly(), '->isHttpOnly() returns the cookie httponly flag'); + } + + public function testGetExpiresTime() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertNull($cookie->getExpiresTime(), '->getExpiresTime() returns the expires time'); + + $cookie = new Cookie('foo', 'bar', $time = time() - 86400); + $this->assertEquals($time, $cookie->getExpiresTime(), '->getExpiresTime() returns the expires time'); + } + + public function testIsExpired() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertFalse($cookie->isExpired(), '->isExpired() returns false when the cookie never expires (null as expires time)'); + + $cookie = new Cookie('foo', 'bar', time() - 86400); + $this->assertTrue($cookie->isExpired(), '->isExpired() returns true when the cookie is expired'); + + $cookie = new Cookie('foo', 'bar', 0); + $this->assertFalse($cookie->isExpired()); + } + + /** + * @expectedException \UnexpectedValueException + * @expectedExceptionMessage The cookie expiration time "string" is not valid. + */ + public function testConstructException() + { + $cookie = new Cookie('foo', 'bar', 'string'); + } +} diff --git a/vendor/symfony/browser-kit/Tests/HistoryTest.php b/vendor/symfony/browser-kit/Tests/HistoryTest.php new file mode 100644 index 00000000..aa09b05b --- /dev/null +++ b/vendor/symfony/browser-kit/Tests/HistoryTest.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\BrowserKit\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\BrowserKit\Request; + +class HistoryTest extends TestCase +{ + public function testAdd() + { + $history = new History(); + $history->add(new Request('http://www.example1.com/', 'get')); + $this->assertSame('http://www.example1.com/', $history->current()->getUri(), '->add() adds a request to the history'); + + $history->add(new Request('http://www.example2.com/', 'get')); + $this->assertSame('http://www.example2.com/', $history->current()->getUri(), '->add() adds a request to the history'); + + $history->add(new Request('http://www.example3.com/', 'get')); + $history->back(); + $history->add(new Request('http://www.example4.com/', 'get')); + $this->assertSame('http://www.example4.com/', $history->current()->getUri(), '->add() adds a request to the history'); + + $history->back(); + $this->assertSame('http://www.example2.com/', $history->current()->getUri(), '->add() adds a request to the history'); + } + + public function testClearIsEmpty() + { + $history = new History(); + $history->add(new Request('http://www.example.com/', 'get')); + + $this->assertFalse($history->isEmpty(), '->isEmpty() returns false if the history is not empty'); + + $history->clear(); + + $this->assertTrue($history->isEmpty(), '->isEmpty() true if the history is empty'); + } + + public function testCurrent() + { + $history = new History(); + + try { + $history->current(); + $this->fail('->current() throws a \LogicException if the history is empty'); + } catch (\Exception $e) { + $this->assertInstanceOf('LogicException', $e, '->current() throws a \LogicException if the history is empty'); + } + + $history->add(new Request('http://www.example.com/', 'get')); + + $this->assertSame('http://www.example.com/', $history->current()->getUri(), '->current() returns the current request in the history'); + } + + public function testBack() + { + $history = new History(); + $history->add(new Request('http://www.example.com/', 'get')); + + try { + $history->back(); + $this->fail('->back() throws a \LogicException if the history is already on the first page'); + } catch (\Exception $e) { + $this->assertInstanceOf('LogicException', $e, '->current() throws a \LogicException if the history is already on the first page'); + } + + $history->add(new Request('http://www.example1.com/', 'get')); + $history->back(); + + $this->assertSame('http://www.example.com/', $history->current()->getUri(), '->back() returns the previous request in the history'); + } + + public function testForward() + { + $history = new History(); + $history->add(new Request('http://www.example.com/', 'get')); + $history->add(new Request('http://www.example1.com/', 'get')); + + try { + $history->forward(); + $this->fail('->forward() throws a \LogicException if the history is already on the last page'); + } catch (\Exception $e) { + $this->assertInstanceOf('LogicException', $e, '->forward() throws a \LogicException if the history is already on the last page'); + } + + $history->back(); + $history->forward(); + + $this->assertSame('http://www.example1.com/', $history->current()->getUri(), '->forward() returns the next request in the history'); + } +} diff --git a/vendor/symfony/browser-kit/Tests/RequestTest.php b/vendor/symfony/browser-kit/Tests/RequestTest.php new file mode 100644 index 00000000..f163de96 --- /dev/null +++ b/vendor/symfony/browser-kit/Tests/RequestTest.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\BrowserKit\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\BrowserKit\Request; + +class RequestTest extends TestCase +{ + public function testGetUri() + { + $request = new Request('http://www.example.com/', 'get'); + $this->assertEquals('http://www.example.com/', $request->getUri(), '->getUri() returns the URI of the request'); + } + + public function testGetMethod() + { + $request = new Request('http://www.example.com/', 'get'); + $this->assertEquals('get', $request->getMethod(), '->getMethod() returns the method of the request'); + } + + public function testGetParameters() + { + $request = new Request('http://www.example.com/', 'get', array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getParameters(), '->getParameters() returns the parameters of the request'); + } + + public function testGetFiles() + { + $request = new Request('http://www.example.com/', 'get', array(), array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getFiles(), '->getFiles() returns the uploaded files of the request'); + } + + public function testGetCookies() + { + $request = new Request('http://www.example.com/', 'get', array(), array(), array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getCookies(), '->getCookies() returns the cookies of the request'); + } + + public function testGetServer() + { + $request = new Request('http://www.example.com/', 'get', array(), array(), array(), array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getServer(), '->getServer() returns the server parameters of the request'); + } +} diff --git a/vendor/symfony/browser-kit/Tests/ResponseTest.php b/vendor/symfony/browser-kit/Tests/ResponseTest.php new file mode 100644 index 00000000..0ba4e3b3 --- /dev/null +++ b/vendor/symfony/browser-kit/Tests/ResponseTest.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\BrowserKit\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\BrowserKit\Response; + +class ResponseTest extends TestCase +{ + public function testGetUri() + { + $response = new Response('foo'); + $this->assertEquals('foo', $response->getContent(), '->getContent() returns the content of the response'); + } + + public function testGetStatus() + { + $response = new Response('foo', 304); + $this->assertEquals('304', $response->getStatus(), '->getStatus() returns the status of the response'); + } + + public function testGetHeaders() + { + $response = new Response('foo', 200, array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $response->getHeaders(), '->getHeaders() returns the headers of the response'); + } + + public function testGetHeader() + { + $response = new Response('foo', 200, array( + 'Content-Type' => 'text/html', + 'Set-Cookie' => array('foo=bar', 'bar=foo'), + )); + + $this->assertEquals('text/html', $response->getHeader('Content-Type'), '->getHeader() returns a header of the response'); + $this->assertEquals('text/html', $response->getHeader('content-type'), '->getHeader() returns a header of the response'); + $this->assertEquals('text/html', $response->getHeader('content_type'), '->getHeader() returns a header of the response'); + $this->assertEquals('foo=bar', $response->getHeader('Set-Cookie'), '->getHeader() returns the first header value'); + $this->assertEquals(array('foo=bar', 'bar=foo'), $response->getHeader('Set-Cookie', false), '->getHeader() returns all header values if first is false'); + + $this->assertNull($response->getHeader('foo'), '->getHeader() returns null if the header is not defined'); + $this->assertEquals(array(), $response->getHeader('foo', false), '->getHeader() returns an empty array if the header is not defined and first is set to false'); + } + + public function testMagicToString() + { + $response = new Response('foo', 304, array('foo' => 'bar')); + + $this->assertEquals("foo: bar\n\nfoo", $response->__toString(), '->__toString() returns the headers and the content as a string'); + } + + public function testMagicToStringWithMultipleSetCookieHeader() + { + $headers = array( + 'content-type' => 'text/html; charset=utf-8', + 'set-cookie' => array('foo=bar', 'bar=foo'), + ); + + $expected = 'content-type: text/html; charset=utf-8'."\n"; + $expected .= 'set-cookie: foo=bar'."\n"; + $expected .= 'set-cookie: bar=foo'."\n\n"; + $expected .= 'foo'; + + $response = new Response('foo', 304, $headers); + + $this->assertEquals($expected, $response->__toString(), '->__toString() returns the headers and the content as a string'); + } +} diff --git a/vendor/symfony/browser-kit/composer.json b/vendor/symfony/browser-kit/composer.json new file mode 100644 index 00000000..a18d66e8 --- /dev/null +++ b/vendor/symfony/browser-kit/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/browser-kit", + "type": "library", + "description": "Symfony BrowserKit 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/dom-crawler": "~2.8|~3.0" + }, + "require-dev": { + "symfony/process": "~2.8|~3.0", + "symfony/css-selector": "~2.8|~3.0" + }, + "suggest": { + "symfony/process": "" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\BrowserKit\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/vendor/symfony/browser-kit/phpunit.xml.dist b/vendor/symfony/browser-kit/phpunit.xml.dist new file mode 100644 index 00000000..fa6d06a8 --- /dev/null +++ b/vendor/symfony/browser-kit/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/class-loader/.gitignore b/vendor/symfony/class-loader/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/class-loader/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/class-loader/ApcClassLoader.php b/vendor/symfony/class-loader/ApcClassLoader.php new file mode 100644 index 00000000..0d899842 --- /dev/null +++ b/vendor/symfony/class-loader/ApcClassLoader.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\ClassLoader; + +@trigger_error('The '.__NAMESPACE__.'\ApcClassLoader class is deprecated since version 3.3 and will be removed in 4.0. Use `composer install --apcu-autoloader` instead.', E_USER_DEPRECATED); + +/** + * ApcClassLoader implements a wrapping autoloader cached in APC for PHP 5.3. + * + * It expects an object implementing a findFile method to find the file. This + * allows using it as a wrapper around the other loaders of the component (the + * ClassLoader for instance) but also around any other autoloaders following + * this convention (the Composer one for instance). + * + * // with a Symfony autoloader + * use Symfony\Component\ClassLoader\ClassLoader; + * + * $loader = new ClassLoader(); + * $loader->addPrefix('Symfony\Component', __DIR__.'/component'); + * $loader->addPrefix('Symfony', __DIR__.'/framework'); + * + * // or with a Composer autoloader + * use Composer\Autoload\ClassLoader; + * + * $loader = new ClassLoader(); + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * $cachedLoader = new ApcClassLoader('my_prefix', $loader); + * + * // activate the cached autoloader + * $cachedLoader->register(); + * + * // eventually deactivate the non-cached loader if it was registered previously + * // to be sure to use the cached one. + * $loader->unregister(); + * + * @author Fabien Potencier + * @author Kris Wallsmith + * + * @deprecated since version 3.3, to be removed in 4.0. Use `composer install --apcu-autoloader` instead. + */ +class ApcClassLoader +{ + private $prefix; + + /** + * A class loader object that implements the findFile() method. + * + * @var object + */ + protected $decorated; + + /** + * Constructor. + * + * @param string $prefix The APC namespace prefix to use + * @param object $decorated A class loader object that implements the findFile() method + * + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function __construct($prefix, $decorated) + { + if (!function_exists('apcu_fetch')) { + throw new \RuntimeException('Unable to use ApcClassLoader as APC is not installed.'); + } + + if (!method_exists($decorated, 'findFile')) { + throw new \InvalidArgumentException('The class finder must implement a "findFile" method.'); + } + + $this->prefix = $prefix; + $this->decorated = $decorated; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * + * @return bool|null True, if loaded + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + require $file; + + return true; + } + } + + /** + * Finds a file by class name while caching lookups to APC. + * + * @param string $class A class name to resolve to file + * + * @return string|null + */ + public function findFile($class) + { + $file = apcu_fetch($this->prefix.$class, $success); + + if (!$success) { + apcu_store($this->prefix.$class, $file = $this->decorated->findFile($class) ?: null); + } + + return $file; + } + + /** + * Passes through all unknown calls onto the decorated object. + */ + public function __call($method, $args) + { + return call_user_func_array(array($this->decorated, $method), $args); + } +} diff --git a/vendor/symfony/class-loader/CHANGELOG.md b/vendor/symfony/class-loader/CHANGELOG.md new file mode 100644 index 00000000..203ec9ee --- /dev/null +++ b/vendor/symfony/class-loader/CHANGELOG.md @@ -0,0 +1,41 @@ +CHANGELOG +========= + +3.3.0 +----- + + * deprecated the component: use Composer instead + +3.0.0 +----- + + * The DebugClassLoader class has been removed + * The DebugUniversalClassLoader class has been removed + * The UniversalClassLoader class has been removed + * The ApcUniversalClassLoader class has been removed + +2.4.0 +----- + + * deprecated the UniversalClassLoader in favor of the ClassLoader class instead + * deprecated the ApcUniversalClassLoader in favor of the ApcClassLoader class instead + * deprecated the DebugUniversalClassLoader in favor of the DebugClassLoader class from the Debug component + * deprecated the DebugClassLoader as it has been moved to the Debug component instead + +2.3.0 +----- + + * added a WinCacheClassLoader for WinCache + +2.1.0 +----- + + * added a DebugClassLoader able to wrap any autoloader providing a findFile + method + * added a new ApcClassLoader and XcacheClassLoader using composition to wrap + other loaders + * added a new ClassLoader which does not distinguish between namespaced and + pear-like classes (as the PEAR convention is a subset of PSR-0) and + supports using Composer's namespace maps + * added a class map generator + * added support for loading globally-installed PEAR packages diff --git a/vendor/symfony/class-loader/ClassCollectionLoader.php b/vendor/symfony/class-loader/ClassCollectionLoader.php new file mode 100644 index 00000000..2be210be --- /dev/null +++ b/vendor/symfony/class-loader/ClassCollectionLoader.php @@ -0,0 +1,450 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader; + +if (\PHP_VERSION_ID >= 70000) { + @trigger_error('The '.__NAMESPACE__.'\ClassCollectionLoader class is deprecated since version 3.3 and will be removed in 4.0.', E_USER_DEPRECATED); +} + +/** + * ClassCollectionLoader. + * + * @author Fabien Potencier + * + * @deprecated since version 3.3, to be removed in 4.0. + */ +class ClassCollectionLoader +{ + private static $loaded; + private static $seen; + private static $useTokenizer = true; + + /** + * Loads a list of classes and caches them in one big file. + * + * @param array $classes An array of classes to load + * @param string $cacheDir A cache directory + * @param string $name The cache name prefix + * @param bool $autoReload Whether to flush the cache when the cache is stale or not + * @param bool $adaptive Whether to remove already declared classes or not + * @param string $extension File extension of the resulting file + * + * @throws \InvalidArgumentException When class can't be loaded + */ + public static function load($classes, $cacheDir, $name, $autoReload, $adaptive = false, $extension = '.php') + { + // each $name can only be loaded once per PHP process + if (isset(self::$loaded[$name])) { + return; + } + + self::$loaded[$name] = true; + + if ($adaptive) { + $declared = array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits()); + + // don't include already declared classes + $classes = array_diff($classes, $declared); + + // the cache is different depending on which classes are already declared + $name = $name.'-'.substr(hash('sha256', implode('|', $classes)), 0, 5); + } + + $classes = array_unique($classes); + + // cache the core classes + if (!is_dir($cacheDir) && !@mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) { + throw new \RuntimeException(sprintf('Class Collection Loader was not able to create directory "%s"', $cacheDir)); + } + $cacheDir = rtrim(realpath($cacheDir) ?: $cacheDir, '/'.DIRECTORY_SEPARATOR); + $cache = $cacheDir.'/'.$name.$extension; + + // auto-reload + $reload = false; + if ($autoReload) { + $metadata = $cache.'.meta'; + if (!is_file($metadata) || !is_file($cache)) { + $reload = true; + } else { + $time = filemtime($cache); + $meta = unserialize(file_get_contents($metadata)); + + sort($meta[1]); + sort($classes); + + if ($meta[1] != $classes) { + $reload = true; + } else { + foreach ($meta[0] as $resource) { + if (!is_file($resource) || filemtime($resource) > $time) { + $reload = true; + + break; + } + } + } + } + } + + if (!$reload && file_exists($cache)) { + require_once $cache; + + return; + } + if (!$adaptive) { + $declared = array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits()); + } + + $files = self::inline($classes, $cache, $declared); + + if ($autoReload) { + // save the resources + self::writeCacheFile($metadata, serialize(array(array_values($files), $classes))); + } + } + + /** + * Generates a file where classes and their parents are inlined. + * + * @param array $classes An array of classes to load + * @param string $cache The file where classes are inlined + * @param array $excluded An array of classes that won't be inlined + * + * @return array The source map of inlined classes, with classes as keys and files as values + * + * @throws \RuntimeException When class can't be loaded + */ + public static function inline($classes, $cache, array $excluded) + { + $declared = array(); + foreach (self::getOrderedClasses($excluded) as $class) { + $declared[$class->getName()] = true; + } + + // cache the core classes + $cacheDir = dirname($cache); + if (!is_dir($cacheDir) && !@mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) { + throw new \RuntimeException(sprintf('Class Collection Loader was not able to create directory "%s"', $cacheDir)); + } + + $spacesRegex = '(?:\s*+(?:(?:\#|//)[^\n]*+\n|/\*(?:(?getName()])) { + continue; + } + $declared[$class->getName()] = true; + + $files[$class->getName()] = $file = $class->getFileName(); + $c = file_get_contents($file); + + if (preg_match($dontInlineRegex, $c)) { + $file = explode('/', str_replace(DIRECTORY_SEPARATOR, '/', $file)); + + for ($i = 0; isset($file[$i], $cacheDir[$i]); ++$i) { + if ($file[$i] !== $cacheDir[$i]) { + break; + } + } + if (1 >= $i) { + $file = var_export(implode('/', $file), true); + } else { + $file = array_slice($file, $i); + $file = str_repeat('../', count($cacheDir) - $i).implode('/', $file); + $file = '__DIR__.'.var_export('/'.$file, true); + } + + $c = "\nnamespace {require $file;}"; + } else { + $c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', $c); + + // fakes namespace declaration for global code + if (!$class->inNamespace()) { + $c = "\nnamespace\n{\n".$c."\n}\n"; + } + + $c = self::fixNamespaceDeclarations('= 70000) { + // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 + unset($tokens, $rawChunk); + gc_mem_caches(); + } + + return $output; + } + + /** + * This method is only useful for testing. + */ + public static function enableTokenizer($bool) + { + self::$useTokenizer = (bool) $bool; + } + + /** + * Strips leading & trailing ws, multiple EOL, multiple ws. + * + * @param string $code Original PHP code + * + * @return string compressed code + */ + private static function compressCode($code) + { + return preg_replace( + array('/^\s+/m', '/\s+$/m', '/([\n\r]+ *[\n\r]+)+/', '/[ \t]+/'), + array('', '', "\n", ' '), + $code + ); + } + + /** + * Writes a cache file. + * + * @param string $file Filename + * @param string $content Temporary file content + * + * @throws \RuntimeException when a cache file cannot be written + */ + private static function writeCacheFile($file, $content) + { + $dir = dirname($file); + if (!is_writable($dir)) { + throw new \RuntimeException(sprintf('Cache directory "%s" is not writable.', $dir)); + } + + $tmpFile = tempnam($dir, basename($file)); + + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) { + @chmod($file, 0666 & ~umask()); + + return; + } + + throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file)); + } + + /** + * Gets an ordered array of passed classes including all their dependencies. + * + * @param array $classes + * + * @return \ReflectionClass[] An array of sorted \ReflectionClass instances (dependencies added if needed) + * + * @throws \InvalidArgumentException When a class can't be loaded + */ + private static function getOrderedClasses(array $classes) + { + $map = array(); + self::$seen = array(); + foreach ($classes as $class) { + try { + $reflectionClass = new \ReflectionClass($class); + } catch (\ReflectionException $e) { + throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class)); + } + + $map = array_merge($map, self::getClassHierarchy($reflectionClass)); + } + + return $map; + } + + private static function getClassHierarchy(\ReflectionClass $class) + { + if (isset(self::$seen[$class->getName()])) { + return array(); + } + + self::$seen[$class->getName()] = true; + + $classes = array($class); + $parent = $class; + while (($parent = $parent->getParentClass()) && $parent->isUserDefined() && !isset(self::$seen[$parent->getName()])) { + self::$seen[$parent->getName()] = true; + + array_unshift($classes, $parent); + } + + $traits = array(); + + foreach ($classes as $c) { + foreach (self::resolveDependencies(self::computeTraitDeps($c), $c) as $trait) { + if ($trait !== $c) { + $traits[] = $trait; + } + } + } + + return array_merge(self::getInterfaces($class), $traits, $classes); + } + + private static function getInterfaces(\ReflectionClass $class) + { + $classes = array(); + + foreach ($class->getInterfaces() as $interface) { + $classes = array_merge($classes, self::getInterfaces($interface)); + } + + if ($class->isUserDefined() && $class->isInterface() && !isset(self::$seen[$class->getName()])) { + self::$seen[$class->getName()] = true; + + $classes[] = $class; + } + + return $classes; + } + + private static function computeTraitDeps(\ReflectionClass $class) + { + $traits = $class->getTraits(); + $deps = array($class->getName() => $traits); + while ($trait = array_pop($traits)) { + if ($trait->isUserDefined() && !isset(self::$seen[$trait->getName()])) { + self::$seen[$trait->getName()] = true; + $traitDeps = $trait->getTraits(); + $deps[$trait->getName()] = $traitDeps; + $traits = array_merge($traits, $traitDeps); + } + } + + return $deps; + } + + /** + * Dependencies resolution. + * + * This function does not check for circular dependencies as it should never + * occur with PHP traits. + * + * @param array $tree The dependency tree + * @param \ReflectionClass $node The node + * @param \ArrayObject $resolved An array of already resolved dependencies + * @param \ArrayObject $unresolved An array of dependencies to be resolved + * + * @return \ArrayObject The dependencies for the given node + * + * @throws \RuntimeException if a circular dependency is detected + */ + private static function resolveDependencies(array $tree, $node, \ArrayObject $resolved = null, \ArrayObject $unresolved = null) + { + if (null === $resolved) { + $resolved = new \ArrayObject(); + } + if (null === $unresolved) { + $unresolved = new \ArrayObject(); + } + $nodeName = $node->getName(); + + if (isset($tree[$nodeName])) { + $unresolved[$nodeName] = $node; + foreach ($tree[$nodeName] as $dependency) { + if (!$resolved->offsetExists($dependency->getName())) { + self::resolveDependencies($tree, $dependency, $resolved, $unresolved); + } + } + $resolved[$nodeName] = $node; + unset($unresolved[$nodeName]); + } + + return $resolved; + } +} diff --git a/vendor/symfony/class-loader/ClassLoader.php b/vendor/symfony/class-loader/ClassLoader.php new file mode 100644 index 00000000..0a72d283 --- /dev/null +++ b/vendor/symfony/class-loader/ClassLoader.php @@ -0,0 +1,207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader; + +@trigger_error('The '.__NAMESPACE__.'\ClassLoader class is deprecated since version 3.3 and will be removed in 4.0. Use Composer instead.', E_USER_DEPRECATED); + +/** + * ClassLoader implements an PSR-0 class loader. + * + * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md + * + * $loader = new ClassLoader(); + * + * // register classes with namespaces + * $loader->addPrefix('Symfony\Component', __DIR__.'/component'); + * $loader->addPrefix('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (e.g. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * + * @deprecated since version 3.3, to be removed in 4.0. + */ +class ClassLoader +{ + private $prefixes = array(); + private $fallbackDirs = array(); + private $useIncludePath = false; + + /** + * Returns prefixes. + * + * @return array + */ + public function getPrefixes() + { + return $this->prefixes; + } + + /** + * Returns fallback directories. + * + * @return array + */ + public function getFallbackDirs() + { + return $this->fallbackDirs; + } + + /** + * Adds prefixes. + * + * @param array $prefixes Prefixes to add + */ + public function addPrefixes(array $prefixes) + { + foreach ($prefixes as $prefix => $path) { + $this->addPrefix($prefix, $path); + } + } + + /** + * Registers a set of classes. + * + * @param string $prefix The classes prefix + * @param array|string $paths The location(s) of the classes + */ + public function addPrefix($prefix, $paths) + { + if (!$prefix) { + foreach ((array) $paths as $path) { + $this->fallbackDirs[] = $path; + } + + return; + } + if (isset($this->prefixes[$prefix])) { + if (is_array($paths)) { + $this->prefixes[$prefix] = array_unique(array_merge( + $this->prefixes[$prefix], + $paths + )); + } elseif (!in_array($paths, $this->prefixes[$prefix])) { + $this->prefixes[$prefix][] = $paths; + } + } else { + $this->prefixes[$prefix] = array_unique((array) $paths); + } + } + + /** + * Turns on searching the include for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = (bool) $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * + * @return bool|null True, if loaded + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + require $file; + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|null The path, if found + */ + public function findFile($class) + { + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $classPath = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, 0, $pos)).DIRECTORY_SEPARATOR; + $className = substr($class, $pos + 1); + } else { + // PEAR-like class name + $classPath = null; + $className = $class; + } + + $classPath .= str_replace('_', DIRECTORY_SEPARATOR, $className).'.php'; + + foreach ($this->prefixes as $prefix => $dirs) { + if ($class === strstr($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($dir.DIRECTORY_SEPARATOR.$classPath)) { + return $dir.DIRECTORY_SEPARATOR.$classPath; + } + } + } + } + + foreach ($this->fallbackDirs as $dir) { + if (file_exists($dir.DIRECTORY_SEPARATOR.$classPath)) { + return $dir.DIRECTORY_SEPARATOR.$classPath; + } + } + + if ($this->useIncludePath && $file = stream_resolve_include_path($classPath)) { + return $file; + } + } +} diff --git a/vendor/symfony/class-loader/ClassMapGenerator.php b/vendor/symfony/class-loader/ClassMapGenerator.php new file mode 100644 index 00000000..12f06ac4 --- /dev/null +++ b/vendor/symfony/class-loader/ClassMapGenerator.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader; + +@trigger_error('The '.__NAMESPACE__.'\ClassMapGenerator class is deprecated since version 3.3 and will be removed in 4.0. Use Composer instead.', E_USER_DEPRECATED); + +/** + * ClassMapGenerator. + * + * @author Gyula Sallai + * + * @deprecated since version 3.3, to be removed in 4.0. + */ +class ClassMapGenerator +{ + /** + * Generate a class map file. + * + * @param array|string $dirs Directories or a single path to search in + * @param string $file The name of the class map file + */ + public static function dump($dirs, $file) + { + $dirs = (array) $dirs; + $maps = array(); + + foreach ($dirs as $dir) { + $maps = array_merge($maps, static::createMap($dir)); + } + + file_put_contents($file, sprintf('isFile()) { + continue; + } + + $path = $file->getRealPath() ?: $file->getPathname(); + + if (pathinfo($path, PATHINFO_EXTENSION) !== 'php') { + continue; + } + + $classes = self::findClasses($path); + + if (\PHP_VERSION_ID >= 70000) { + // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 + gc_mem_caches(); + } + + foreach ($classes as $class) { + $map[$class] = $path; + } + } + + return $map; + } + + /** + * Extract the classes in the given file. + * + * @param string $path The file to check + * + * @return array The found classes + */ + private static function findClasses($path) + { + $contents = file_get_contents($path); + $tokens = token_get_all($contents); + + $classes = array(); + + $namespace = ''; + for ($i = 0; isset($tokens[$i]); ++$i) { + $token = $tokens[$i]; + + if (!isset($token[1])) { + continue; + } + + $class = ''; + + switch ($token[0]) { + case T_NAMESPACE: + $namespace = ''; + // If there is a namespace, extract it + while (isset($tokens[++$i][1])) { + if (in_array($tokens[$i][0], array(T_STRING, T_NS_SEPARATOR))) { + $namespace .= $tokens[$i][1]; + } + } + $namespace .= '\\'; + break; + case T_CLASS: + case T_INTERFACE: + case T_TRAIT: + // Skip usage of ::class constant + $isClassConstant = false; + for ($j = $i - 1; $j > 0; --$j) { + if (!isset($tokens[$j][1])) { + break; + } + + if (T_DOUBLE_COLON === $tokens[$j][0]) { + $isClassConstant = true; + break; + } elseif (!in_array($tokens[$j][0], array(T_WHITESPACE, T_DOC_COMMENT, T_COMMENT))) { + break; + } + } + + if ($isClassConstant) { + break; + } + + // Find the classname + while (isset($tokens[++$i][1])) { + $t = $tokens[$i]; + if (T_STRING === $t[0]) { + $class .= $t[1]; + } elseif ('' !== $class && T_WHITESPACE === $t[0]) { + break; + } + } + + $classes[] = ltrim($namespace.$class, '\\'); + break; + default: + break; + } + } + + return $classes; + } +} diff --git a/vendor/symfony/class-loader/LICENSE b/vendor/symfony/class-loader/LICENSE new file mode 100644 index 00000000..17d16a13 --- /dev/null +++ b/vendor/symfony/class-loader/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/vendor/symfony/class-loader/MapClassLoader.php b/vendor/symfony/class-loader/MapClassLoader.php new file mode 100644 index 00000000..43f1cd03 --- /dev/null +++ b/vendor/symfony/class-loader/MapClassLoader.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\ClassLoader; + +@trigger_error('The '.__NAMESPACE__.'\MapClassLoader class is deprecated since version 3.3 and will be removed in 4.0. Use Composer instead.', E_USER_DEPRECATED); + +/** + * A class loader that uses a mapping file to look up paths. + * + * @author Fabien Potencier + * + * @deprecated since version 3.3, to be removed in 4.0. + */ +class MapClassLoader +{ + private $map = array(); + + /** + * Constructor. + * + * @param array $map A map where keys are classes and values the absolute file path + */ + public function __construct(array $map) + { + $this->map = $map; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + */ + public function loadClass($class) + { + if (isset($this->map[$class])) { + require $this->map[$class]; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|null The path, if found + */ + public function findFile($class) + { + if (isset($this->map[$class])) { + return $this->map[$class]; + } + } +} diff --git a/vendor/symfony/class-loader/Psr4ClassLoader.php b/vendor/symfony/class-loader/Psr4ClassLoader.php new file mode 100644 index 00000000..ee3d7cf4 --- /dev/null +++ b/vendor/symfony/class-loader/Psr4ClassLoader.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\ClassLoader; + +@trigger_error('The '.__NAMESPACE__.'\Psr4ClassLoader class is deprecated since version 3.3 and will be removed in 4.0. Use Composer instead.', E_USER_DEPRECATED); + +/** + * A PSR-4 compatible class loader. + * + * See http://www.php-fig.org/psr/psr-4/ + * + * @author Alexander M. Turek + * + * @deprecated since version 3.3, to be removed in 4.0. + */ +class Psr4ClassLoader +{ + /** + * @var array + */ + private $prefixes = array(); + + /** + * @param string $prefix + * @param string $baseDir + */ + public function addPrefix($prefix, $baseDir) + { + $prefix = trim($prefix, '\\').'\\'; + $baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; + $this->prefixes[] = array($prefix, $baseDir); + } + + /** + * @param string $class + * + * @return string|null + */ + public function findFile($class) + { + $class = ltrim($class, '\\'); + + foreach ($this->prefixes as list($currentPrefix, $currentBaseDir)) { + if (0 === strpos($class, $currentPrefix)) { + $classWithoutPrefix = substr($class, strlen($currentPrefix)); + $file = $currentBaseDir.str_replace('\\', DIRECTORY_SEPARATOR, $classWithoutPrefix).'.php'; + if (file_exists($file)) { + return $file; + } + } + } + } + + /** + * @param string $class + * + * @return bool + */ + public function loadClass($class) + { + $file = $this->findFile($class); + if (null !== $file) { + require $file; + + return true; + } + + return false; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Removes this instance from the registered autoloaders. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } +} diff --git a/vendor/symfony/class-loader/README.md b/vendor/symfony/class-loader/README.md new file mode 100644 index 00000000..d61992b6 --- /dev/null +++ b/vendor/symfony/class-loader/README.md @@ -0,0 +1,14 @@ +ClassLoader Component +===================== + +The ClassLoader component provides tools to autoload your classes and cache +their locations for performance. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/class_loader/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/vendor/symfony/class-loader/Tests/ApcClassLoaderTest.php b/vendor/symfony/class-loader/Tests/ApcClassLoaderTest.php new file mode 100644 index 00000000..bd02f546 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/ApcClassLoaderTest.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ClassLoader\ApcClassLoader; +use Symfony\Component\ClassLoader\ClassLoader; + +/** + * @group legacy + */ +class ApcClassLoaderTest extends TestCase +{ + protected function setUp() + { + if (!(ini_get('apc.enabled') && ini_get('apc.enable_cli'))) { + $this->markTestSkipped('The apc extension is not enabled.'); + } else { + apcu_clear_cache(); + } + } + + protected function tearDown() + { + if (ini_get('apc.enabled') && ini_get('apc.enable_cli')) { + apcu_clear_cache(); + } + } + + public function testConstructor() + { + $loader = new ClassLoader(); + $loader->addPrefix('Apc\Namespaced', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + + $loader = new ApcClassLoader('test.prefix.', $loader); + + $this->assertEquals($loader->findFile('\Apc\Namespaced\FooBar'), apcu_fetch('test.prefix.\Apc\Namespaced\FooBar'), '__construct() takes a prefix as its first argument'); + } + + /** + * @dataProvider getLoadClassTests + */ + public function testLoadClass($className, $testClassName, $message) + { + $loader = new ClassLoader(); + $loader->addPrefix('Apc\Namespaced', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Apc_Pearlike_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + + $loader = new ApcClassLoader('test.prefix.', $loader); + $loader->loadClass($testClassName); + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassTests() + { + return array( + array('\\Apc\\Namespaced\\Foo', 'Apc\\Namespaced\\Foo', '->loadClass() loads Apc\Namespaced\Foo class'), + array('Apc_Pearlike_Foo', 'Apc_Pearlike_Foo', '->loadClass() loads Apc_Pearlike_Foo class'), + ); + } + + /** + * @dataProvider getLoadClassFromFallbackTests + */ + public function testLoadClassFromFallback($className, $testClassName, $message) + { + $loader = new ClassLoader(); + $loader->addPrefix('Apc\Namespaced', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Apc_Pearlike_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('', array(__DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/fallback')); + + $loader = new ApcClassLoader('test.prefix.fallback', $loader); + $loader->loadClass($testClassName); + + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassFromFallbackTests() + { + return array( + array('\\Apc\\Namespaced\\Baz', 'Apc\\Namespaced\\Baz', '->loadClass() loads Apc\Namespaced\Baz class'), + array('Apc_Pearlike_Baz', 'Apc_Pearlike_Baz', '->loadClass() loads Apc_Pearlike_Baz class'), + array('\\Apc\\Namespaced\\FooBar', 'Apc\\Namespaced\\FooBar', '->loadClass() loads Apc\Namespaced\Baz class from fallback dir'), + array('Apc_Pearlike_FooBar', 'Apc_Pearlike_FooBar', '->loadClass() loads Apc_Pearlike_Baz class from fallback dir'), + ); + } + + /** + * @dataProvider getLoadClassNamespaceCollisionTests + */ + public function testLoadClassNamespaceCollision($namespaces, $className, $message) + { + $loader = new ClassLoader(); + $loader->addPrefixes($namespaces); + + $loader = new ApcClassLoader('test.prefix.collision.', $loader); + $loader->loadClass($className); + + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassNamespaceCollisionTests() + { + return array( + array( + array( + 'Apc\\NamespaceCollision\\A' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha', + 'Apc\\NamespaceCollision\\A\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta', + ), + 'Apc\NamespaceCollision\A\Foo', + '->loadClass() loads NamespaceCollision\A\Foo from alpha.', + ), + array( + array( + 'Apc\\NamespaceCollision\\A\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta', + 'Apc\\NamespaceCollision\\A' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha', + ), + 'Apc\NamespaceCollision\A\Bar', + '->loadClass() loads NamespaceCollision\A\Bar from alpha.', + ), + array( + array( + 'Apc\\NamespaceCollision\\A' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha', + 'Apc\\NamespaceCollision\\A\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta', + ), + 'Apc\NamespaceCollision\A\B\Foo', + '->loadClass() loads NamespaceCollision\A\B\Foo from beta.', + ), + array( + array( + 'Apc\\NamespaceCollision\\A\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta', + 'Apc\\NamespaceCollision\\A' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha', + ), + 'Apc\NamespaceCollision\A\B\Bar', + '->loadClass() loads NamespaceCollision\A\B\Bar from beta.', + ), + ); + } + + /** + * @dataProvider getLoadClassPrefixCollisionTests + */ + public function testLoadClassPrefixCollision($prefixes, $className, $message) + { + $loader = new ClassLoader(); + $loader->addPrefixes($prefixes); + + $loader = new ApcClassLoader('test.prefix.collision.', $loader); + $loader->loadClass($className); + + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassPrefixCollisionTests() + { + return array( + array( + array( + 'ApcPrefixCollision_A_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha/Apc', + 'ApcPrefixCollision_A_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta/Apc', + ), + 'ApcPrefixCollision_A_Foo', + '->loadClass() loads ApcPrefixCollision_A_Foo from alpha.', + ), + array( + array( + 'ApcPrefixCollision_A_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta/Apc', + 'ApcPrefixCollision_A_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha/Apc', + ), + 'ApcPrefixCollision_A_Bar', + '->loadClass() loads ApcPrefixCollision_A_Bar from alpha.', + ), + array( + array( + 'ApcPrefixCollision_A_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha/Apc', + 'ApcPrefixCollision_A_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta/Apc', + ), + 'ApcPrefixCollision_A_B_Foo', + '->loadClass() loads ApcPrefixCollision_A_B_Foo from beta.', + ), + array( + array( + 'ApcPrefixCollision_A_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta/Apc', + 'ApcPrefixCollision_A_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha/Apc', + ), + 'ApcPrefixCollision_A_B_Bar', + '->loadClass() loads ApcPrefixCollision_A_B_Bar from beta.', + ), + ); + } +} diff --git a/vendor/symfony/class-loader/Tests/ClassCollectionLoaderTest.php b/vendor/symfony/class-loader/Tests/ClassCollectionLoaderTest.php new file mode 100644 index 00000000..da34082b --- /dev/null +++ b/vendor/symfony/class-loader/Tests/ClassCollectionLoaderTest.php @@ -0,0 +1,319 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ClassLoader\ClassCollectionLoader; +use Symfony\Component\ClassLoader\Tests\Fixtures\DeclaredClass; +use Symfony\Component\ClassLoader\Tests\Fixtures\WarmedClass; + +require_once __DIR__.'/Fixtures/ClassesWithParents/GInterface.php'; +require_once __DIR__.'/Fixtures/ClassesWithParents/CInterface.php'; +require_once __DIR__.'/Fixtures/ClassesWithParents/B.php'; +require_once __DIR__.'/Fixtures/ClassesWithParents/A.php'; + +/** + * @group legacy + */ +class ClassCollectionLoaderTest extends TestCase +{ + public function testTraitDependencies() + { + require_once __DIR__.'/Fixtures/deps/traits.php'; + + $r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader'); + $m = $r->getMethod('getOrderedClasses'); + $m->setAccessible(true); + + $ordered = $m->invoke(null, array('CTFoo')); + + $this->assertEquals( + array('TD', 'TC', 'TB', 'TA', 'TZ', 'CTFoo'), + array_map(function ($class) { return $class->getName(); }, $ordered) + ); + + $ordered = $m->invoke(null, array('CTBar')); + + $this->assertEquals( + array('TD', 'TZ', 'TC', 'TB', 'TA', 'CTBar'), + array_map(function ($class) { return $class->getName(); }, $ordered) + ); + } + + /** + * @dataProvider getDifferentOrders + */ + public function testClassReordering(array $classes) + { + $expected = array( + 'ClassesWithParents\\GInterface', + 'ClassesWithParents\\CInterface', + 'ClassesWithParents\\B', + 'ClassesWithParents\\A', + ); + + $r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader'); + $m = $r->getMethod('getOrderedClasses'); + $m->setAccessible(true); + + $ordered = $m->invoke(null, $classes); + + $this->assertEquals($expected, array_map(function ($class) { return $class->getName(); }, $ordered)); + } + + public function getDifferentOrders() + { + return array( + array(array( + 'ClassesWithParents\\A', + 'ClassesWithParents\\CInterface', + 'ClassesWithParents\\GInterface', + 'ClassesWithParents\\B', + )), + array(array( + 'ClassesWithParents\\B', + 'ClassesWithParents\\A', + 'ClassesWithParents\\CInterface', + )), + array(array( + 'ClassesWithParents\\CInterface', + 'ClassesWithParents\\B', + 'ClassesWithParents\\A', + )), + array(array( + 'ClassesWithParents\\A', + )), + ); + } + + /** + * @dataProvider getDifferentOrdersForTraits + */ + public function testClassWithTraitsReordering(array $classes) + { + require_once __DIR__.'/Fixtures/ClassesWithParents/ATrait.php'; + require_once __DIR__.'/Fixtures/ClassesWithParents/BTrait.php'; + require_once __DIR__.'/Fixtures/ClassesWithParents/CTrait.php'; + require_once __DIR__.'/Fixtures/ClassesWithParents/D.php'; + require_once __DIR__.'/Fixtures/ClassesWithParents/E.php'; + + $expected = array( + 'ClassesWithParents\\GInterface', + 'ClassesWithParents\\CInterface', + 'ClassesWithParents\\ATrait', + 'ClassesWithParents\\BTrait', + 'ClassesWithParents\\CTrait', + 'ClassesWithParents\\B', + 'ClassesWithParents\\A', + 'ClassesWithParents\\D', + 'ClassesWithParents\\E', + ); + + $r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader'); + $m = $r->getMethod('getOrderedClasses'); + $m->setAccessible(true); + + $ordered = $m->invoke(null, $classes); + + $this->assertEquals($expected, array_map(function ($class) { return $class->getName(); }, $ordered)); + } + + public function getDifferentOrdersForTraits() + { + return array( + array(array( + 'ClassesWithParents\\E', + 'ClassesWithParents\\ATrait', + )), + array(array( + 'ClassesWithParents\\E', + )), + ); + } + + public function testFixClassWithTraitsOrdering() + { + require_once __DIR__.'/Fixtures/ClassesWithParents/CTrait.php'; + require_once __DIR__.'/Fixtures/ClassesWithParents/F.php'; + require_once __DIR__.'/Fixtures/ClassesWithParents/G.php'; + + $classes = array( + 'ClassesWithParents\\F', + 'ClassesWithParents\\G', + ); + + $expected = array( + 'ClassesWithParents\\CTrait', + 'ClassesWithParents\\F', + 'ClassesWithParents\\G', + ); + + $r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader'); + $m = $r->getMethod('getOrderedClasses'); + $m->setAccessible(true); + + $ordered = $m->invoke(null, $classes); + + $this->assertEquals($expected, array_map(function ($class) { return $class->getName(); }, $ordered)); + } + + /** + * @dataProvider getFixNamespaceDeclarationsData + */ + public function testFixNamespaceDeclarations($source, $expected) + { + $this->assertEquals('assertEquals('assertEquals(<<<'EOF' +namespace Namespaced +{ +class WithComments +{ +public static $loaded = true; +} +$string ='string should not be modified {$string}'; +$heredoc = (<<assertTrue(class_exists(WarmedClass::class, true)); + + @unlink($cache = sys_get_temp_dir().'/inline.php'); + + $classes = array(WarmedClass::class); + $excluded = array(DeclaredClass::class); + + ClassCollectionLoader::inline($classes, $cache, $excluded); + + $this->assertSame(<<<'EOTXT' + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ClassLoader\ClassLoader; + +/** + * @group legacy + */ +class ClassLoaderTest extends TestCase +{ + public function testGetPrefixes() + { + $loader = new ClassLoader(); + $loader->addPrefix('Foo', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Bar', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Bas', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $prefixes = $loader->getPrefixes(); + $this->assertArrayHasKey('Foo', $prefixes); + $this->assertArrayNotHasKey('Foo1', $prefixes); + $this->assertArrayHasKey('Bar', $prefixes); + $this->assertArrayHasKey('Bas', $prefixes); + } + + public function testGetFallbackDirs() + { + $loader = new ClassLoader(); + $loader->addPrefix(null, __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix(null, __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $fallback_dirs = $loader->getFallbackDirs(); + $this->assertCount(2, $fallback_dirs); + } + + /** + * @dataProvider getLoadClassTests + */ + public function testLoadClass($className, $testClassName, $message) + { + $loader = new ClassLoader(); + $loader->addPrefix('Namespaced2\\', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Pearlike2_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->loadClass($testClassName); + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassTests() + { + return array( + array('\\Namespaced2\\Foo', 'Namespaced2\\Foo', '->loadClass() loads Namespaced2\Foo class'), + array('\\Pearlike2_Foo', 'Pearlike2_Foo', '->loadClass() loads Pearlike2_Foo class'), + ); + } + + /** + * @dataProvider getLoadNonexistentClassTests + */ + public function testLoadNonexistentClass($className, $testClassName, $message) + { + $loader = new ClassLoader(); + $loader->addPrefix('Namespaced2\\', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Pearlike2_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->loadClass($testClassName); + $this->assertFalse(class_exists($className), $message); + } + + public function getLoadNonexistentClassTests() + { + return array( + array('\\Pearlike3_Bar', '\\Pearlike3_Bar', '->loadClass() loads non existing Pearlike3_Bar class with a leading slash'), + ); + } + + public function testAddPrefixSingle() + { + $loader = new ClassLoader(); + $loader->addPrefix('Foo', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Foo', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $prefixes = $loader->getPrefixes(); + $this->assertArrayHasKey('Foo', $prefixes); + $this->assertCount(1, $prefixes['Foo']); + } + + public function testAddPrefixesSingle() + { + $loader = new ClassLoader(); + $loader->addPrefixes(array('Foo' => array('foo', 'foo'))); + $loader->addPrefixes(array('Foo' => array('foo'))); + $prefixes = $loader->getPrefixes(); + $this->assertArrayHasKey('Foo', $prefixes); + $this->assertCount(1, $prefixes['Foo'], print_r($prefixes, true)); + } + + public function testAddPrefixMulti() + { + $loader = new ClassLoader(); + $loader->addPrefix('Foo', 'foo'); + $loader->addPrefix('Foo', 'bar'); + $prefixes = $loader->getPrefixes(); + $this->assertArrayHasKey('Foo', $prefixes); + $this->assertCount(2, $prefixes['Foo']); + $this->assertContains('foo', $prefixes['Foo']); + $this->assertContains('bar', $prefixes['Foo']); + } + + public function testUseIncludePath() + { + $loader = new ClassLoader(); + $this->assertFalse($loader->getUseIncludePath()); + + $this->assertNull($loader->findFile('Foo')); + + $includePath = get_include_path(); + + $loader->setUseIncludePath(true); + $this->assertTrue($loader->getUseIncludePath()); + + set_include_path(__DIR__.'/Fixtures/includepath'.PATH_SEPARATOR.$includePath); + + $this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'includepath'.DIRECTORY_SEPARATOR.'Foo.php', $loader->findFile('Foo')); + + set_include_path($includePath); + } + + /** + * @dataProvider getLoadClassFromFallbackTests + */ + public function testLoadClassFromFallback($className, $testClassName, $message) + { + $loader = new ClassLoader(); + $loader->addPrefix('Namespaced2\\', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('Pearlike2_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $loader->addPrefix('', array(__DIR__.DIRECTORY_SEPARATOR.'Fixtures/fallback')); + $loader->loadClass($testClassName); + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassFromFallbackTests() + { + return array( + array('\\Namespaced2\\Baz', 'Namespaced2\\Baz', '->loadClass() loads Namespaced2\Baz class'), + array('\\Pearlike2_Baz', 'Pearlike2_Baz', '->loadClass() loads Pearlike2_Baz class'), + array('\\Namespaced2\\FooBar', 'Namespaced2\\FooBar', '->loadClass() loads Namespaced2\Baz class from fallback dir'), + array('\\Pearlike2_FooBar', 'Pearlike2_FooBar', '->loadClass() loads Pearlike2_Baz class from fallback dir'), + ); + } + + /** + * @dataProvider getLoadClassNamespaceCollisionTests + */ + public function testLoadClassNamespaceCollision($namespaces, $className, $message) + { + $loader = new ClassLoader(); + $loader->addPrefixes($namespaces); + + $loader->loadClass($className); + $this->assertTrue(class_exists($className), $message); + } + + public function getLoadClassNamespaceCollisionTests() + { + return array( + array( + array( + 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + ), + 'NamespaceCollision\C\Foo', + '->loadClass() loads NamespaceCollision\C\Foo from alpha.', + ), + array( + array( + 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + ), + 'NamespaceCollision\C\Bar', + '->loadClass() loads NamespaceCollision\C\Bar from alpha.', + ), + array( + array( + 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + ), + 'NamespaceCollision\C\B\Foo', + '->loadClass() loads NamespaceCollision\C\B\Foo from beta.', + ), + array( + array( + 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + ), + 'NamespaceCollision\C\B\Bar', + '->loadClass() loads NamespaceCollision\C\B\Bar from beta.', + ), + array( + array( + 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + ), + 'PrefixCollision_C_Foo', + '->loadClass() loads PrefixCollision_C_Foo from alpha.', + ), + array( + array( + 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + ), + 'PrefixCollision_C_Bar', + '->loadClass() loads PrefixCollision_C_Bar from alpha.', + ), + array( + array( + 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + ), + 'PrefixCollision_C_B_Foo', + '->loadClass() loads PrefixCollision_C_B_Foo from beta.', + ), + array( + array( + 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta', + 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha', + ), + 'PrefixCollision_C_B_Bar', + '->loadClass() loads PrefixCollision_C_B_Bar from beta.', + ), + ); + } +} diff --git a/vendor/symfony/class-loader/Tests/ClassMapGeneratorTest.php b/vendor/symfony/class-loader/Tests/ClassMapGeneratorTest.php new file mode 100644 index 00000000..69b1e615 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/ClassMapGeneratorTest.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ClassLoader\ClassMapGenerator; + +/** + * @group legacy + */ +class ClassMapGeneratorTest extends TestCase +{ + /** + * @var string|null + */ + private $workspace = null; + + public function prepare_workspace() + { + $this->workspace = sys_get_temp_dir().'/'.microtime(true).'.'.mt_rand(); + mkdir($this->workspace, 0777, true); + $this->workspace = realpath($this->workspace); + } + + /** + * @param string $file + */ + private function clean($file) + { + if (is_dir($file) && !is_link($file)) { + $dir = new \FilesystemIterator($file); + foreach ($dir as $childFile) { + $this->clean($childFile); + } + + rmdir($file); + } else { + unlink($file); + } + } + + /** + * @dataProvider getTestCreateMapTests + */ + public function testDump($directory) + { + $this->prepare_workspace(); + + $file = $this->workspace.'/file'; + + $generator = new ClassMapGenerator(); + $generator->dump($directory, $file); + $this->assertFileExists($file); + + $this->clean($this->workspace); + } + + /** + * @dataProvider getTestCreateMapTests + */ + public function testCreateMap($directory, $expected) + { + $this->assertEqualsNormalized($expected, ClassMapGenerator::createMap($directory)); + } + + public function getTestCreateMapTests() + { + $data = array( + array(__DIR__.'/Fixtures/Namespaced', array( + 'Namespaced\\Bar' => realpath(__DIR__).'/Fixtures/Namespaced/Bar.php', + 'Namespaced\\Foo' => realpath(__DIR__).'/Fixtures/Namespaced/Foo.php', + 'Namespaced\\Baz' => realpath(__DIR__).'/Fixtures/Namespaced/Baz.php', + 'Namespaced\\WithComments' => realpath(__DIR__).'/Fixtures/Namespaced/WithComments.php', + 'Namespaced\\WithStrictTypes' => realpath(__DIR__).'/Fixtures/Namespaced/WithStrictTypes.php', + 'Namespaced\\WithHaltCompiler' => realpath(__DIR__).'/Fixtures/Namespaced/WithHaltCompiler.php', + 'Namespaced\\WithDirMagic' => realpath(__DIR__).'/Fixtures/Namespaced/WithDirMagic.php', + 'Namespaced\\WithFileMagic' => realpath(__DIR__).'/Fixtures/Namespaced/WithFileMagic.php', + )), + array(__DIR__.'/Fixtures/beta/NamespaceCollision', array( + 'NamespaceCollision\\A\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Bar.php', + 'NamespaceCollision\\A\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Foo.php', + 'NamespaceCollision\\C\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Bar.php', + 'NamespaceCollision\\C\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Foo.php', + )), + array(__DIR__.'/Fixtures/Pearlike', array( + 'Pearlike_Foo' => realpath(__DIR__).'/Fixtures/Pearlike/Foo.php', + 'Pearlike_Bar' => realpath(__DIR__).'/Fixtures/Pearlike/Bar.php', + 'Pearlike_Baz' => realpath(__DIR__).'/Fixtures/Pearlike/Baz.php', + 'Pearlike_WithComments' => realpath(__DIR__).'/Fixtures/Pearlike/WithComments.php', + )), + array(__DIR__.'/Fixtures/classmap', array( + 'Foo\\Bar\\A' => realpath(__DIR__).'/Fixtures/classmap/sameNsMultipleClasses.php', + 'Foo\\Bar\\B' => realpath(__DIR__).'/Fixtures/classmap/sameNsMultipleClasses.php', + 'A' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php', + 'Alpha\\A' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php', + 'Alpha\\B' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php', + 'Beta\\A' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php', + 'Beta\\B' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php', + 'ClassMap\\SomeInterface' => realpath(__DIR__).'/Fixtures/classmap/SomeInterface.php', + 'ClassMap\\SomeParent' => realpath(__DIR__).'/Fixtures/classmap/SomeParent.php', + 'ClassMap\\SomeClass' => realpath(__DIR__).'/Fixtures/classmap/SomeClass.php', + )), + array(__DIR__.'/Fixtures/php5.4', array( + 'TFoo' => __DIR__.'/Fixtures/php5.4/traits.php', + 'CFoo' => __DIR__.'/Fixtures/php5.4/traits.php', + 'Foo\\TBar' => __DIR__.'/Fixtures/php5.4/traits.php', + 'Foo\\IBar' => __DIR__.'/Fixtures/php5.4/traits.php', + 'Foo\\TFooBar' => __DIR__.'/Fixtures/php5.4/traits.php', + 'Foo\\CBar' => __DIR__.'/Fixtures/php5.4/traits.php', + )), + array(__DIR__.'/Fixtures/php5.5', array( + 'ClassCons\\Foo' => __DIR__.'/Fixtures/php5.5/class_cons.php', + )), + ); + + return $data; + } + + public function testCreateMapFinderSupport() + { + $finder = new \Symfony\Component\Finder\Finder(); + $finder->files()->in(__DIR__.'/Fixtures/beta/NamespaceCollision'); + + $this->assertEqualsNormalized(array( + 'NamespaceCollision\\A\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Bar.php', + 'NamespaceCollision\\A\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Foo.php', + 'NamespaceCollision\\C\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Bar.php', + 'NamespaceCollision\\C\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Foo.php', + ), ClassMapGenerator::createMap($finder)); + } + + protected function assertEqualsNormalized($expected, $actual, $message = null) + { + foreach ($expected as $ns => $path) { + $expected[$ns] = str_replace('\\', '/', $path); + } + foreach ($actual as $ns => $path) { + $actual[$ns] = str_replace('\\', '/', $path); + } + $this->assertEquals($expected, $actual, $message); + } +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Bar.php b/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Bar.php new file mode 100644 index 00000000..4259f145 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Bar.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\Namespaced; + +class Bar +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Baz.php b/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Baz.php new file mode 100644 index 00000000..3ddb595e --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Baz.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\Namespaced; + +class Baz +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Foo.php b/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Foo.php new file mode 100644 index 00000000..cf0a4b74 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Foo.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\Namespaced; + +class Foo +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/FooBar.php b/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/FooBar.php new file mode 100644 index 00000000..bbbc8151 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/FooBar.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\Namespaced; + +class FooBar +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/Apc/Pearlike/Bar.php b/vendor/symfony/class-loader/Tests/Fixtures/Apc/Pearlike/Bar.php new file mode 100644 index 00000000..e774cb9b --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/Apc/Pearlike/Bar.php @@ -0,0 +1,6 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\NamespaceCollision\A; + +class Bar +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/Apc/alpha/Apc/NamespaceCollision/A/Foo.php b/vendor/symfony/class-loader/Tests/Fixtures/Apc/alpha/Apc/NamespaceCollision/A/Foo.php new file mode 100644 index 00000000..184a1b1d --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/Apc/alpha/Apc/NamespaceCollision/A/Foo.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\NamespaceCollision\A; + +class Foo +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/ApcPrefixCollision/A/B/Bar.php b/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/ApcPrefixCollision/A/B/Bar.php new file mode 100644 index 00000000..3892f706 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/ApcPrefixCollision/A/B/Bar.php @@ -0,0 +1,6 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\NamespaceCollision\A\B; + +class Bar +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/NamespaceCollision/A/B/Foo.php b/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/NamespaceCollision/A/B/Foo.php new file mode 100644 index 00000000..450eeb50 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/NamespaceCollision/A/B/Foo.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\NamespaceCollision\A\B; + +class Foo +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/Apc/fallback/Apc/Pearlike/FooBar.php b/vendor/symfony/class-loader/Tests/Fixtures/Apc/fallback/Apc/Pearlike/FooBar.php new file mode 100644 index 00000000..96f2f76c --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/Apc/fallback/Apc/Pearlike/FooBar.php @@ -0,0 +1,6 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Apc\Namespaced; + +class FooBar +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/ClassesWithParents/A.php b/vendor/symfony/class-loader/Tests/Fixtures/ClassesWithParents/A.php new file mode 100644 index 00000000..b0f94259 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/ClassesWithParents/A.php @@ -0,0 +1,7 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Namespaced; + +class Bar +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Baz.php b/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Baz.php new file mode 100644 index 00000000..0b0bbd05 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Baz.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Namespaced; + +class Baz +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Foo.php b/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Foo.php new file mode 100644 index 00000000..df5e1f4c --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Foo.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Namespaced; + +class Foo +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/WithComments.php b/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/WithComments.php new file mode 100644 index 00000000..361e53de --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/WithComments.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 Namespaced; + +class WithComments +{ + /** @Boolean */ + public static $loaded = true; +} + +$string = 'string should not be modified {$string}'; + +$heredoc = (<< + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +class Pearlike_WithComments +{ + /** @Boolean */ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/Pearlike2/Bar.php b/vendor/symfony/class-loader/Tests/Fixtures/Pearlike2/Bar.php new file mode 100644 index 00000000..7f5f7977 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/Pearlike2/Bar.php @@ -0,0 +1,6 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace NamespaceCollision\A; + +class Bar +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/A/Foo.php b/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/A/Foo.php new file mode 100644 index 00000000..aee6a080 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/A/Foo.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace NamespaceCollision\A; + +class Foo +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/C/Bar.php b/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/C/Bar.php new file mode 100644 index 00000000..c1b8dd65 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/C/Bar.php @@ -0,0 +1,8 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace NamespaceCollision\A\B; + +class Bar +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/A/B/Foo.php b/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/A/B/Foo.php new file mode 100644 index 00000000..f5f2d727 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/A/B/Foo.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace NamespaceCollision\A\B; + +class Foo +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/C/B/Bar.php b/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/C/B/Bar.php new file mode 100644 index 00000000..4bb03dc7 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/C/B/Bar.php @@ -0,0 +1,8 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassMap; + +class SomeClass extends SomeParent implements SomeInterface +{ +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeInterface.php b/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeInterface.php new file mode 100644 index 00000000..1fe5e09a --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassMap; + +interface SomeInterface +{ +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeParent.php b/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeParent.php new file mode 100644 index 00000000..ce2f9fc6 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeParent.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ClassMap; + +abstract class SomeParent +{ +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/classmap/multipleNs.php b/vendor/symfony/class-loader/Tests/Fixtures/classmap/multipleNs.php new file mode 100644 index 00000000..c7cec646 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/classmap/multipleNs.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 Foo\Bar; + +class A +{ +} +class B +{ +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/deps/traits.php b/vendor/symfony/class-loader/Tests/Fixtures/deps/traits.php new file mode 100644 index 00000000..82b30a6f --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/deps/traits.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 Namespaced; + +class FooBar +{ + public static $loaded = true; +} diff --git a/vendor/symfony/class-loader/Tests/Fixtures/fallback/Namespaced2/FooBar.php b/vendor/symfony/class-loader/Tests/Fixtures/fallback/Namespaced2/FooBar.php new file mode 100644 index 00000000..1036d435 --- /dev/null +++ b/vendor/symfony/class-loader/Tests/Fixtures/fallback/Namespaced2/FooBar.php @@ -0,0 +1,8 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ClassLoader\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ClassLoader\Psr4ClassLoader; + +/** + * @group legacy + */ +class Psr4ClassLoaderTest extends TestCase +{ + /** + * @param string $className + * @dataProvider getLoadClassTests + */ + public function testLoadClass($className) + { + $loader = new Psr4ClassLoader(); + $loader->addPrefix( + 'Acme\\DemoLib', + __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'psr-4' + ); + $loader->loadClass($className); + $this->assertTrue(class_exists($className), sprintf('loadClass() should load %s', $className)); + } + + /** + * @return array + */ + public function getLoadClassTests() + { + return array( + array('Acme\\DemoLib\\Foo'), + array('Acme\\DemoLib\\Class_With_Underscores'), + array('Acme\\DemoLib\\Lets\\Go\\Deeper\\Foo'), + array('Acme\\DemoLib\\Lets\\Go\\Deeper\\Class_With_Underscores'), + ); + } + + /** + * @param string $className + * @dataProvider getLoadNonexistentClassTests + */ + public function testLoadNonexistentClass($className) + { + $loader = new Psr4ClassLoader(); + $loader->addPrefix( + 'Acme\\DemoLib', + __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'psr-4' + ); + $loader->loadClass($className); + $this->assertFalse(class_exists($className), sprintf('loadClass() should not load %s', $className)); + } + + /** + * @return array + */ + public function getLoadNonexistentClassTests() + { + return array( + array('Acme\\DemoLib\\I_Do_Not_Exist'), + array('UnknownVendor\\SomeLib\\I_Do_Not_Exist'), + ); + } +} diff --git a/vendor/symfony/class-loader/WinCacheClassLoader.php b/vendor/symfony/class-loader/WinCacheClassLoader.php new file mode 100644 index 00000000..76b13d39 --- /dev/null +++ b/vendor/symfony/class-loader/WinCacheClassLoader.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\ClassLoader; + +@trigger_error('The '.__NAMESPACE__.'\WinCacheClassLoader class is deprecated since version 3.3 and will be removed in 4.0. Use `composer install --apcu-autoloader` instead.', E_USER_DEPRECATED); + +/** + * WinCacheClassLoader implements a wrapping autoloader cached in WinCache. + * + * It expects an object implementing a findFile method to find the file. This + * allow using it as a wrapper around the other loaders of the component (the + * ClassLoader for instance) but also around any other autoloaders following + * this convention (the Composer one for instance). + * + * // with a Symfony autoloader + * $loader = new ClassLoader(); + * $loader->addPrefix('Symfony\Component', __DIR__.'/component'); + * $loader->addPrefix('Symfony', __DIR__.'/framework'); + * + * // or with a Composer autoloader + * use Composer\Autoload\ClassLoader; + * + * $loader = new ClassLoader(); + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * $cachedLoader = new WinCacheClassLoader('my_prefix', $loader); + * + * // activate the cached autoloader + * $cachedLoader->register(); + * + * // eventually deactivate the non-cached loader if it was registered previously + * // to be sure to use the cached one. + * $loader->unregister(); + * + * @author Fabien Potencier + * @author Kris Wallsmith + * @author Artem Ryzhkov + * + * @deprecated since version 3.3, to be removed in 4.0. Use `composer install --apcu-autoloader` instead. + */ +class WinCacheClassLoader +{ + private $prefix; + + /** + * A class loader object that implements the findFile() method. + * + * @var object + */ + protected $decorated; + + /** + * Constructor. + * + * @param string $prefix The WinCache namespace prefix to use + * @param object $decorated A class loader object that implements the findFile() method + * + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function __construct($prefix, $decorated) + { + if (!extension_loaded('wincache')) { + throw new \RuntimeException('Unable to use WinCacheClassLoader as WinCache is not enabled.'); + } + + if (!method_exists($decorated, 'findFile')) { + throw new \InvalidArgumentException('The class finder must implement a "findFile" method.'); + } + + $this->prefix = $prefix; + $this->decorated = $decorated; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * + * @return bool|null True, if loaded + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + require $file; + + return true; + } + } + + /** + * Finds a file by class name while caching lookups to WinCache. + * + * @param string $class A class name to resolve to file + * + * @return string|null + */ + public function findFile($class) + { + $file = wincache_ucache_get($this->prefix.$class, $success); + + if (!$success) { + wincache_ucache_set($this->prefix.$class, $file = $this->decorated->findFile($class) ?: null, 0); + } + + return $file; + } + + /** + * Passes through all unknown calls onto the decorated object. + */ + public function __call($method, $args) + { + return call_user_func_array(array($this->decorated, $method), $args); + } +} diff --git a/vendor/symfony/class-loader/XcacheClassLoader.php b/vendor/symfony/class-loader/XcacheClassLoader.php new file mode 100644 index 00000000..d3ea09b2 --- /dev/null +++ b/vendor/symfony/class-loader/XcacheClassLoader.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\ClassLoader; + +@trigger_error('The '.__NAMESPACE__.'\XcacheClassLoader class is deprecated since version 3.3 and will be removed in 4.0. Use `composer install --apcu-autoloader` instead.', E_USER_DEPRECATED); + +/** + * XcacheClassLoader implements a wrapping autoloader cached in XCache for PHP 5.3. + * + * It expects an object implementing a findFile method to find the file. This + * allows using it as a wrapper around the other loaders of the component (the + * ClassLoader for instance) but also around any other autoloaders following + * this convention (the Composer one for instance). + * + * // with a Symfony autoloader + * $loader = new ClassLoader(); + * $loader->addPrefix('Symfony\Component', __DIR__.'/component'); + * $loader->addPrefix('Symfony', __DIR__.'/framework'); + * + * // or with a Composer autoloader + * use Composer\Autoload\ClassLoader; + * + * $loader = new ClassLoader(); + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * $cachedLoader = new XcacheClassLoader('my_prefix', $loader); + * + * // activate the cached autoloader + * $cachedLoader->register(); + * + * // eventually deactivate the non-cached loader if it was registered previously + * // to be sure to use the cached one. + * $loader->unregister(); + * + * @author Fabien Potencier + * @author Kris Wallsmith + * @author Kim Hemsø Rasmussen + * + * @deprecated since version 3.3, to be removed in 4.0. Use `composer install --apcu-autoloader` instead. + */ +class XcacheClassLoader +{ + private $prefix; + + /** + * A class loader object that implements the findFile() method. + * + * @var object + */ + private $decorated; + + /** + * Constructor. + * + * @param string $prefix The XCache namespace prefix to use + * @param object $decorated A class loader object that implements the findFile() method + * + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function __construct($prefix, $decorated) + { + if (!extension_loaded('xcache')) { + throw new \RuntimeException('Unable to use XcacheClassLoader as XCache is not enabled.'); + } + + if (!method_exists($decorated, 'findFile')) { + throw new \InvalidArgumentException('The class finder must implement a "findFile" method.'); + } + + $this->prefix = $prefix; + $this->decorated = $decorated; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * + * @return bool|null True, if loaded + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + require $file; + + return true; + } + } + + /** + * Finds a file by class name while caching lookups to Xcache. + * + * @param string $class A class name to resolve to file + * + * @return string|null + */ + public function findFile($class) + { + if (xcache_isset($this->prefix.$class)) { + $file = xcache_get($this->prefix.$class); + } else { + $file = $this->decorated->findFile($class) ?: null; + xcache_set($this->prefix.$class, $file); + } + + return $file; + } + + /** + * Passes through all unknown calls onto the decorated object. + */ + public function __call($method, $args) + { + return call_user_func_array(array($this->decorated, $method), $args); + } +} diff --git a/vendor/symfony/class-loader/composer.json b/vendor/symfony/class-loader/composer.json new file mode 100644 index 00000000..a1dfc3fa --- /dev/null +++ b/vendor/symfony/class-loader/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/class-loader", + "type": "library", + "description": "Symfony ClassLoader Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "minimum-stability": "dev", + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/finder": "~2.8|~3.0", + "symfony/polyfill-apcu": "~1.1" + }, + "suggest": { + "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\ClassLoader\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/vendor/symfony/class-loader/phpunit.xml.dist b/vendor/symfony/class-loader/phpunit.xml.dist new file mode 100644 index 00000000..5158b22f --- /dev/null +++ b/vendor/symfony/class-loader/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/config/.gitignore b/vendor/symfony/config/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/config/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/config/CHANGELOG.md b/vendor/symfony/config/CHANGELOG.md new file mode 100644 index 00000000..4ef4e625 --- /dev/null +++ b/vendor/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/vendor/symfony/config/ConfigCache.php b/vendor/symfony/config/ConfigCache.php new file mode 100644 index 00000000..591c89bc --- /dev/null +++ b/vendor/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/vendor/symfony/config/ConfigCacheFactory.php b/vendor/symfony/config/ConfigCacheFactory.php new file mode 100644 index 00000000..396536e2 --- /dev/null +++ b/vendor/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/vendor/symfony/config/ConfigCacheFactoryInterface.php b/vendor/symfony/config/ConfigCacheFactoryInterface.php new file mode 100644 index 00000000..bd614c4b --- /dev/null +++ b/vendor/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/vendor/symfony/config/ConfigCacheInterface.php b/vendor/symfony/config/ConfigCacheInterface.php new file mode 100644 index 00000000..7c47ad70 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/ArrayNode.php b/vendor/symfony/config/Definition/ArrayNode.php new file mode 100644 index 00000000..457e7a8c --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/BaseNode.php b/vendor/symfony/config/Definition/BaseNode.php new file mode 100644 index 00000000..dbf36335 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/BooleanNode.php b/vendor/symfony/config/Definition/BooleanNode.php new file mode 100644 index 00000000..08e1a773 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php b/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php new file mode 100644 index 00000000..9db0b367 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php b/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php new file mode 100644 index 00000000..28e56579 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php b/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php new file mode 100644 index 00000000..56047ad5 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/ExprBuilder.php b/vendor/symfony/config/Definition/Builder/ExprBuilder.php new file mode 100644 index 00000000..bc83b760 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/FloatNodeDefinition.php b/vendor/symfony/config/Definition/Builder/FloatNodeDefinition.php new file mode 100644 index 00000000..c0bed462 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/IntegerNodeDefinition.php b/vendor/symfony/config/Definition/Builder/IntegerNodeDefinition.php new file mode 100644 index 00000000..f6c3c147 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/MergeBuilder.php b/vendor/symfony/config/Definition/Builder/MergeBuilder.php new file mode 100644 index 00000000..1d24953d --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/NodeBuilder.php b/vendor/symfony/config/Definition/Builder/NodeBuilder.php new file mode 100644 index 00000000..e780777a --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/NodeDefinition.php b/vendor/symfony/config/Definition/Builder/NodeDefinition.php new file mode 100644 index 00000000..1b712a31 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/NodeParentInterface.php b/vendor/symfony/config/Definition/Builder/NodeParentInterface.php new file mode 100644 index 00000000..305e9931 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php b/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php new file mode 100644 index 00000000..ae642a2d --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php b/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php new file mode 100644 index 00000000..0d0207ca --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php b/vendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php new file mode 100644 index 00000000..575495bb --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/ScalarNodeDefinition.php b/vendor/symfony/config/Definition/Builder/ScalarNodeDefinition.php new file mode 100644 index 00000000..6170555c --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/TreeBuilder.php b/vendor/symfony/config/Definition/Builder/TreeBuilder.php new file mode 100644 index 00000000..5d02848a --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/ValidationBuilder.php b/vendor/symfony/config/Definition/Builder/ValidationBuilder.php new file mode 100644 index 00000000..12aa59a4 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Builder/VariableNodeDefinition.php b/vendor/symfony/config/Definition/Builder/VariableNodeDefinition.php new file mode 100644 index 00000000..a46b7ea6 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/ConfigurationInterface.php b/vendor/symfony/config/Definition/ConfigurationInterface.php new file mode 100644 index 00000000..d6456edb --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php b/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php new file mode 100644 index 00000000..ec5460f2 --- /dev/null +++ b/vendor/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.''; + } + + $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.''; + } + } + } + } + + // render children tags + foreach ($rootChildren as $child) { + $this->writeLine(''); + $this->writeNode($child, $depth + 4); + } + + // render end tag + if (!$rootIsEmptyTag && !$rootIsVariablePrototype) { + $this->writeLine(''); + + $rootEndTag = ''; + $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/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php b/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php new file mode 100644 index 00000000..16076354 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/EnumNode.php b/vendor/symfony/config/Definition/EnumNode.php new file mode 100644 index 00000000..9b4c4156 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Exception/DuplicateKeyException.php b/vendor/symfony/config/Definition/Exception/DuplicateKeyException.php new file mode 100644 index 00000000..48dd9325 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Exception/Exception.php b/vendor/symfony/config/Definition/Exception/Exception.php new file mode 100644 index 00000000..8933a492 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.php b/vendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.php new file mode 100644 index 00000000..726c07fb --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php b/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php new file mode 100644 index 00000000..3dbc57b1 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Exception/InvalidDefinitionException.php b/vendor/symfony/config/Definition/Exception/InvalidDefinitionException.php new file mode 100644 index 00000000..98310dae --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Exception/InvalidTypeException.php b/vendor/symfony/config/Definition/Exception/InvalidTypeException.php new file mode 100644 index 00000000..d7ca8c9d --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Exception/UnsetKeyException.php b/vendor/symfony/config/Definition/Exception/UnsetKeyException.php new file mode 100644 index 00000000..863181a3 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/FloatNode.php b/vendor/symfony/config/Definition/FloatNode.php new file mode 100644 index 00000000..5e1af17a --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/IntegerNode.php b/vendor/symfony/config/Definition/IntegerNode.php new file mode 100644 index 00000000..ba230702 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/NodeInterface.php b/vendor/symfony/config/Definition/NodeInterface.php new file mode 100644 index 00000000..b9bddc49 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/NumericNode.php b/vendor/symfony/config/Definition/NumericNode.php new file mode 100644 index 00000000..439935e4 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/Processor.php b/vendor/symfony/config/Definition/Processor.php new file mode 100644 index 00000000..025e6937 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/PrototypeNodeInterface.php b/vendor/symfony/config/Definition/PrototypeNodeInterface.php new file mode 100644 index 00000000..8bbb84d4 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/PrototypedArrayNode.php b/vendor/symfony/config/Definition/PrototypedArrayNode.php new file mode 100644 index 00000000..1c3c2188 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/ScalarNode.php b/vendor/symfony/config/Definition/ScalarNode.php new file mode 100644 index 00000000..6b3fd0b6 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Definition/VariableNode.php b/vendor/symfony/config/Definition/VariableNode.php new file mode 100644 index 00000000..a9c35284 --- /dev/null +++ b/vendor/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/vendor/symfony/config/DependencyInjection/ConfigCachePass.php b/vendor/symfony/config/DependencyInjection/ConfigCachePass.php new file mode 100644 index 00000000..02cae0d2 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.php b/vendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.php new file mode 100644 index 00000000..6a3b01cf --- /dev/null +++ b/vendor/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/vendor/symfony/config/Exception/FileLoaderLoadException.php b/vendor/symfony/config/Exception/FileLoaderLoadException.php new file mode 100644 index 00000000..564f75ce --- /dev/null +++ b/vendor/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/vendor/symfony/config/Exception/FileLocatorFileNotFoundException.php b/vendor/symfony/config/Exception/FileLocatorFileNotFoundException.php new file mode 100644 index 00000000..af764eb4 --- /dev/null +++ b/vendor/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/vendor/symfony/config/FileLocator.php b/vendor/symfony/config/FileLocator.php new file mode 100644 index 00000000..16bb5d48 --- /dev/null +++ b/vendor/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($name, $currentPath = null, $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/vendor/symfony/config/FileLocatorInterface.php b/vendor/symfony/config/FileLocatorInterface.php new file mode 100644 index 00000000..cf3c2e59 --- /dev/null +++ b/vendor/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($name, $currentPath = null, $first = true); +} diff --git a/vendor/symfony/config/LICENSE b/vendor/symfony/config/LICENSE new file mode 100644 index 00000000..17d16a13 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Loader/DelegatingLoader.php b/vendor/symfony/config/Loader/DelegatingLoader.php new file mode 100644 index 00000000..23b62565 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Loader/FileLoader.php b/vendor/symfony/config/Loader/FileLoader.php new file mode 100644 index 00000000..02aa811f --- /dev/null +++ b/vendor/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/vendor/symfony/config/Loader/GlobFileLoader.php b/vendor/symfony/config/Loader/GlobFileLoader.php new file mode 100644 index 00000000..f432b45b --- /dev/null +++ b/vendor/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/vendor/symfony/config/Loader/Loader.php b/vendor/symfony/config/Loader/Loader.php new file mode 100644 index 00000000..d2f2ec90 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Loader/LoaderInterface.php b/vendor/symfony/config/Loader/LoaderInterface.php new file mode 100644 index 00000000..dd0a85a6 --- /dev/null +++ b/vendor/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, $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, $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/vendor/symfony/config/Loader/LoaderResolver.php b/vendor/symfony/config/Loader/LoaderResolver.php new file mode 100644 index 00000000..dc6846df --- /dev/null +++ b/vendor/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/vendor/symfony/config/Loader/LoaderResolverInterface.php b/vendor/symfony/config/Loader/LoaderResolverInterface.php new file mode 100644 index 00000000..40f1a1a6 --- /dev/null +++ b/vendor/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/vendor/symfony/config/README.md b/vendor/symfony/config/README.md new file mode 100644 index 00000000..bf400da1 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Resource/ClassExistenceResource.php b/vendor/symfony/config/Resource/ClassExistenceResource.php new file mode 100644 index 00000000..8a86a420 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Resource/ComposerResource.php b/vendor/symfony/config/Resource/ComposerResource.php new file mode 100644 index 00000000..56224d16 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Resource/DirectoryResource.php b/vendor/symfony/config/Resource/DirectoryResource.php new file mode 100644 index 00000000..07280b4b --- /dev/null +++ b/vendor/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/vendor/symfony/config/Resource/FileExistenceResource.php b/vendor/symfony/config/Resource/FileExistenceResource.php new file mode 100644 index 00000000..349402ed --- /dev/null +++ b/vendor/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/vendor/symfony/config/Resource/FileResource.php b/vendor/symfony/config/Resource/FileResource.php new file mode 100644 index 00000000..6ad13051 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Resource/GlobResource.php b/vendor/symfony/config/Resource/GlobResource.php new file mode 100644 index 00000000..67625201 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Resource/ReflectionClassResource.php b/vendor/symfony/config/Resource/ReflectionClassResource.php new file mode 100644 index 00000000..a8e1f961 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Resource/ResourceInterface.php b/vendor/symfony/config/Resource/ResourceInterface.php new file mode 100644 index 00000000..d98fd427 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Resource/SelfCheckingResourceChecker.php b/vendor/symfony/config/Resource/SelfCheckingResourceChecker.php new file mode 100644 index 00000000..d72203bc --- /dev/null +++ b/vendor/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/vendor/symfony/config/Resource/SelfCheckingResourceInterface.php b/vendor/symfony/config/Resource/SelfCheckingResourceInterface.php new file mode 100644 index 00000000..b3260f2b --- /dev/null +++ b/vendor/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($timestamp); +} diff --git a/vendor/symfony/config/ResourceCheckerConfigCache.php b/vendor/symfony/config/ResourceCheckerConfigCache.php new file mode 100644 index 00000000..c0aef1d8 --- /dev/null +++ b/vendor/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/vendor/symfony/config/ResourceCheckerConfigCacheFactory.php b/vendor/symfony/config/ResourceCheckerConfigCacheFactory.php new file mode 100644 index 00000000..ee75efb9 --- /dev/null +++ b/vendor/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/vendor/symfony/config/ResourceCheckerInterface.php b/vendor/symfony/config/ResourceCheckerInterface.php new file mode 100644 index 00000000..612d7778 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/ConfigCacheFactoryTest.php b/vendor/symfony/config/Tests/ConfigCacheFactoryTest.php new file mode 100644 index 00000000..c523e5cd --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/ConfigCacheTest.php b/vendor/symfony/config/Tests/ConfigCacheTest.php new file mode 100644 index 00000000..bf8131d5 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/ArrayNodeTest.php b/vendor/symfony/config/Tests/Definition/ArrayNodeTest.php new file mode 100644 index 00000000..0b5565e0 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/BooleanNodeTest.php b/vendor/symfony/config/Tests/Definition/BooleanNodeTest.php new file mode 100644 index 00000000..ab1d3164 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php b/vendor/symfony/config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php new file mode 100644 index 00000000..f2a32351 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/Builder/BooleanNodeDefinitionTest.php b/vendor/symfony/config/Tests/Definition/Builder/BooleanNodeDefinitionTest.php new file mode 100644 index 00000000..291dd602 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/Builder/EnumNodeDefinitionTest.php b/vendor/symfony/config/Tests/Definition/Builder/EnumNodeDefinitionTest.php new file mode 100644 index 00000000..0f09b78e --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/Builder/ExprBuilderTest.php b/vendor/symfony/config/Tests/Definition/Builder/ExprBuilderTest.php new file mode 100644 index 00000000..99a10413 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/Builder/NodeBuilderTest.php b/vendor/symfony/config/Tests/Definition/Builder/NodeBuilderTest.php new file mode 100644 index 00000000..88775656 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/Builder/NumericNodeDefinitionTest.php b/vendor/symfony/config/Tests/Definition/Builder/NumericNodeDefinitionTest.php new file mode 100644 index 00000000..5a86e1ef --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/Builder/TreeBuilderTest.php b/vendor/symfony/config/Tests/Definition/Builder/TreeBuilderTest.php new file mode 100644 index 00000000..13304fae --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/Dumper/XmlReferenceDumperTest.php b/vendor/symfony/config/Tests/Definition/Dumper/XmlReferenceDumperTest.php new file mode 100644 index 00000000..fd5f9c8c --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/Dumper/YamlReferenceDumperTest.php b/vendor/symfony/config/Tests/Definition/Dumper/YamlReferenceDumperTest.php new file mode 100644 index 00000000..ea5687d6 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/EnumNodeTest.php b/vendor/symfony/config/Tests/Definition/EnumNodeTest.php new file mode 100644 index 00000000..e3e7ef05 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/FinalizationTest.php b/vendor/symfony/config/Tests/Definition/FinalizationTest.php new file mode 100644 index 00000000..733f6008 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/FloatNodeTest.php b/vendor/symfony/config/Tests/Definition/FloatNodeTest.php new file mode 100644 index 00000000..b7ec12fa --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/IntegerNodeTest.php b/vendor/symfony/config/Tests/Definition/IntegerNodeTest.php new file mode 100644 index 00000000..55e8a137 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/MergeTest.php b/vendor/symfony/config/Tests/Definition/MergeTest.php new file mode 100644 index 00000000..e539e25f --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/NormalizationTest.php b/vendor/symfony/config/Tests/Definition/NormalizationTest.php new file mode 100644 index 00000000..f011f542 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Definition/PrototypedArrayNodeTest.php b/vendor/symfony/config/Tests/Definition/PrototypedArrayNodeTest.php new file mode 100644 index 00000000..1a5de41c --- /dev/null +++ b/vendor/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: + * + * + * + * + * + * 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: + * + * + * + * + * + * 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/vendor/symfony/config/Tests/Definition/ScalarNodeTest.php b/vendor/symfony/config/Tests/Definition/ScalarNodeTest.php new file mode 100644 index 00000000..a402a61e --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/DependencyInjection/ConfigCachePassTest.php b/vendor/symfony/config/Tests/DependencyInjection/ConfigCachePassTest.php new file mode 100644 index 00000000..7452755f --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Exception/FileLoaderLoadExceptionTest.php b/vendor/symfony/config/Tests/Exception/FileLoaderLoadExceptionTest.php new file mode 100644 index 00000000..7c5e167c --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/FileLocatorTest.php b/vendor/symfony/config/Tests/FileLocatorTest.php new file mode 100644 index 00000000..049c0090 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Fixtures/Again/foo.xml b/vendor/symfony/config/Tests/Fixtures/Again/foo.xml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/config/Tests/Fixtures/BarNode.php b/vendor/symfony/config/Tests/Fixtures/BarNode.php new file mode 100644 index 00000000..0b9c32de --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Fixtures/Builder/BarNodeDefinition.php b/vendor/symfony/config/Tests/Fixtures/Builder/BarNodeDefinition.php new file mode 100644 index 00000000..0d46f3d2 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Fixtures/Builder/NodeBuilder.php b/vendor/symfony/config/Tests/Fixtures/Builder/NodeBuilder.php new file mode 100644 index 00000000..aa598631 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Fixtures/Builder/VariableNodeDefinition.php b/vendor/symfony/config/Tests/Fixtures/Builder/VariableNodeDefinition.php new file mode 100644 index 00000000..1017880c --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Fixtures/Configuration/ExampleConfiguration.php b/vendor/symfony/config/Tests/Fixtures/Configuration/ExampleConfiguration.php new file mode 100644 index 00000000..3a34f906 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Fixtures/Resource/.hiddenFile b/vendor/symfony/config/Tests/Fixtures/Resource/.hiddenFile new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/config/Tests/Fixtures/Resource/ConditionalClass.php b/vendor/symfony/config/Tests/Fixtures/Resource/ConditionalClass.php new file mode 100644 index 00000000..2ba48c5b --- /dev/null +++ b/vendor/symfony/config/Tests/Fixtures/Resource/ConditionalClass.php @@ -0,0 +1,9 @@ + +]> + diff --git a/vendor/symfony/config/Tests/Fixtures/Util/invalid.xml b/vendor/symfony/config/Tests/Fixtures/Util/invalid.xml new file mode 100644 index 00000000..a07af9fd --- /dev/null +++ b/vendor/symfony/config/Tests/Fixtures/Util/invalid.xml @@ -0,0 +1,2 @@ + + diff --git a/vendor/symfony/config/Tests/Fixtures/Util/invalid_schema.xml b/vendor/symfony/config/Tests/Fixtures/Util/invalid_schema.xml new file mode 100644 index 00000000..e2725a2c --- /dev/null +++ b/vendor/symfony/config/Tests/Fixtures/Util/invalid_schema.xml @@ -0,0 +1,2 @@ + + diff --git a/vendor/symfony/config/Tests/Fixtures/Util/schema.xsd b/vendor/symfony/config/Tests/Fixtures/Util/schema.xsd new file mode 100644 index 00000000..e56820f6 --- /dev/null +++ b/vendor/symfony/config/Tests/Fixtures/Util/schema.xsd @@ -0,0 +1,9 @@ + + + + + + diff --git a/vendor/symfony/config/Tests/Fixtures/Util/valid.xml b/vendor/symfony/config/Tests/Fixtures/Util/valid.xml new file mode 100644 index 00000000..a96bb382 --- /dev/null +++ b/vendor/symfony/config/Tests/Fixtures/Util/valid.xml @@ -0,0 +1,3 @@ + + + diff --git a/vendor/symfony/config/Tests/Fixtures/foo.xml b/vendor/symfony/config/Tests/Fixtures/foo.xml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/config/Tests/Loader/DelegatingLoaderTest.php b/vendor/symfony/config/Tests/Loader/DelegatingLoaderTest.php new file mode 100644 index 00000000..4a01d26a --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Loader/FileLoaderTest.php b/vendor/symfony/config/Tests/Loader/FileLoaderTest.php new file mode 100644 index 00000000..c6e283c7 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Loader/LoaderResolverTest.php b/vendor/symfony/config/Tests/Loader/LoaderResolverTest.php new file mode 100644 index 00000000..0bf56b61 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Loader/LoaderTest.php b/vendor/symfony/config/Tests/Loader/LoaderTest.php new file mode 100644 index 00000000..fefb1d72 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Resource/ClassExistenceResourceTest.php b/vendor/symfony/config/Tests/Resource/ClassExistenceResourceTest.php new file mode 100644 index 00000000..3daef17e --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Resource/ComposerResourceTest.php b/vendor/symfony/config/Tests/Resource/ComposerResourceTest.php new file mode 100644 index 00000000..6857c766 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Resource/DirectoryResourceTest.php b/vendor/symfony/config/Tests/Resource/DirectoryResourceTest.php new file mode 100644 index 00000000..99c75f10 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Resource/FileExistenceResourceTest.php b/vendor/symfony/config/Tests/Resource/FileExistenceResourceTest.php new file mode 100644 index 00000000..433f65e8 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Resource/FileResourceTest.php b/vendor/symfony/config/Tests/Resource/FileResourceTest.php new file mode 100644 index 00000000..97781ffa --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Resource/GlobResourceTest.php b/vendor/symfony/config/Tests/Resource/GlobResourceTest.php new file mode 100644 index 00000000..b84cc9d3 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Resource/ReflectionClassResourceTest.php b/vendor/symfony/config/Tests/Resource/ReflectionClassResourceTest.php new file mode 100644 index 00000000..4eca3d50 --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Resource/ResourceStub.php b/vendor/symfony/config/Tests/Resource/ResourceStub.php new file mode 100644 index 00000000..b01729cb --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/ResourceCheckerConfigCacheTest.php b/vendor/symfony/config/Tests/ResourceCheckerConfigCacheTest.php new file mode 100644 index 00000000..d39742ad --- /dev/null +++ b/vendor/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/vendor/symfony/config/Tests/Util/XmlUtilsTest.php b/vendor/symfony/config/Tests/Util/XmlUtilsTest.php new file mode 100644 index 00000000..161dc617 --- /dev/null +++ b/vendor/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')), 'bartext'), + 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'), 'hello2', 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/vendor/symfony/config/Util/XmlUtils.php b/vendor/symfony/config/Util/XmlUtils.php new file mode 100644 index 00000000..25d9b0a0 --- /dev/null +++ b/vendor/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/vendor/symfony/config/composer.json b/vendor/symfony/config/composer.json new file mode 100644 index 00000000..88090d46 --- /dev/null +++ b/vendor/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/vendor/symfony/config/phpunit.xml.dist b/vendor/symfony/config/phpunit.xml.dist new file mode 100644 index 00000000..36ef339f --- /dev/null +++ b/vendor/symfony/config/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/console/.gitignore b/vendor/symfony/console/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/console/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/console/Application.php b/vendor/symfony/console/Application.php new file mode 100644 index 00000000..fd15fb4c --- /dev/null +++ b/vendor/symfony/console/Application.php @@ -0,0 +1,1137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\DebugFormatterHelper; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\ProcessHelper; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\StreamableInputInterface; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputAwareInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\HelpCommand; +use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleExceptionEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Debug\Exception\FatalThrowableError; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * An Application is the container for a collection of commands. + * + * It is the main entry point of a Console application. + * + * This class is optimized for a standard CLI environment. + * + * Usage: + * + * $app = new Application('myapp', '1.0 (stable)'); + * $app->add(new SimpleCommand()); + * $app->run(); + * + * @author Fabien Potencier + */ +class Application +{ + private $commands = array(); + private $wantHelps = false; + private $runningCommand; + private $name; + private $version; + private $catchExceptions = true; + private $autoExit = true; + private $definition; + private $helperSet; + private $dispatcher; + private $terminal; + private $defaultCommand; + private $singleCommand; + + /** + * @param string $name The name of the application + * @param string $version The version of the application + */ + public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') + { + $this->name = $name; + $this->version = $version; + $this->terminal = new Terminal(); + $this->defaultCommand = 'list'; + $this->helperSet = $this->getDefaultHelperSet(); + $this->definition = $this->getDefaultInputDefinition(); + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } + + public function setDispatcher(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * Runs the current application. + * + * @param InputInterface $input An Input instance + * @param OutputInterface $output An Output instance + * + * @return int 0 if everything went fine, or an error code + * + * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}. + */ + public function run(InputInterface $input = null, OutputInterface $output = null) + { + putenv('LINES='.$this->terminal->getHeight()); + putenv('COLUMNS='.$this->terminal->getWidth()); + + if (null === $input) { + $input = new ArgvInput(); + } + + if (null === $output) { + $output = new ConsoleOutput(); + } + + if (null !== $this->dispatcher && $this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) { + @trigger_error(sprintf('The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead.'), E_USER_DEPRECATED); + } + + $this->configureIO($input, $output); + + try { + $e = null; + $exitCode = $this->doRun($input, $output); + } catch (\Exception $x) { + $e = $x; + } catch (\Throwable $x) { + $e = new FatalThrowableError($x); + } + + if (null !== $e) { + if (!$this->catchExceptions || !$x instanceof \Exception) { + throw $x; + } + + if ($output instanceof ConsoleOutputInterface) { + $this->renderException($e, $output->getErrorOutput()); + } else { + $this->renderException($e, $output); + } + + $exitCode = $e->getCode(); + if (is_numeric($exitCode)) { + $exitCode = (int) $exitCode; + if (0 === $exitCode) { + $exitCode = 1; + } + } else { + $exitCode = 1; + } + } + + if ($this->autoExit) { + if ($exitCode > 255) { + $exitCode = 255; + } + + exit($exitCode); + } + + return $exitCode; + } + + /** + * Runs the current application. + * + * @param InputInterface $input An Input instance + * @param OutputInterface $output An Output instance + * + * @return int 0 if everything went fine, or an error code + */ + public function doRun(InputInterface $input, OutputInterface $output) + { + if (true === $input->hasParameterOption(array('--version', '-V'), true)) { + $output->writeln($this->getLongVersion()); + + return 0; + } + + $name = $this->getCommandName($input); + if (true === $input->hasParameterOption(array('--help', '-h'), true)) { + if (!$name) { + $name = 'help'; + $input = new ArrayInput(array('command_name' => $this->defaultCommand)); + } else { + $this->wantHelps = true; + } + } + + if (!$name) { + $name = $this->defaultCommand; + $input = new ArrayInput(array('command' => $this->defaultCommand)); + } + + try { + $e = $this->runningCommand = null; + // the command name MUST be the first element of the input + $command = $this->find($name); + } catch (\Exception $e) { + } catch (\Throwable $e) { + } + if (null !== $e) { + if (null !== $this->dispatcher) { + $event = new ConsoleErrorEvent($input, $output, $e); + $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event); + $e = $event->getError(); + + if (0 === $event->getExitCode()) { + return 0; + } + } + + throw $e; + } + + $this->runningCommand = $command; + $exitCode = $this->doRunCommand($command, $input, $output); + $this->runningCommand = null; + + return $exitCode; + } + + /** + * Set a helper set to be used with the command. + * + * @param HelperSet $helperSet The helper set + */ + public function setHelperSet(HelperSet $helperSet) + { + $this->helperSet = $helperSet; + } + + /** + * Get the helper set associated with the command. + * + * @return HelperSet The HelperSet instance associated with this command + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Set an input definition to be used with this application. + * + * @param InputDefinition $definition The input definition + */ + public function setDefinition(InputDefinition $definition) + { + $this->definition = $definition; + } + + /** + * Gets the InputDefinition related to this Application. + * + * @return InputDefinition The InputDefinition instance + */ + public function getDefinition() + { + if ($this->singleCommand) { + $inputDefinition = $this->definition; + $inputDefinition->setArguments(); + + return $inputDefinition; + } + + return $this->definition; + } + + /** + * Gets the help message. + * + * @return string A help message + */ + public function getHelp() + { + return $this->getLongVersion(); + } + + /** + * Gets whether to catch exceptions or not during commands execution. + * + * @return bool Whether to catch exceptions or not during commands execution + */ + public function areExceptionsCaught() + { + return $this->catchExceptions; + } + + /** + * Sets whether to catch exceptions or not during commands execution. + * + * @param bool $boolean Whether to catch exceptions or not during commands execution + */ + public function setCatchExceptions($boolean) + { + $this->catchExceptions = (bool) $boolean; + } + + /** + * Gets whether to automatically exit after a command execution or not. + * + * @return bool Whether to automatically exit after a command execution or not + */ + public function isAutoExitEnabled() + { + return $this->autoExit; + } + + /** + * Sets whether to automatically exit after a command execution or not. + * + * @param bool $boolean Whether to automatically exit after a command execution or not + */ + public function setAutoExit($boolean) + { + $this->autoExit = (bool) $boolean; + } + + /** + * Gets the name of the application. + * + * @return string The application name + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the application name. + * + * @param string $name The application name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Gets the application version. + * + * @return string The application version + */ + public function getVersion() + { + return $this->version; + } + + /** + * Sets the application version. + * + * @param string $version The application version + */ + public function setVersion($version) + { + $this->version = $version; + } + + /** + * Returns the long version of the application. + * + * @return string The long application version + */ + public function getLongVersion() + { + if ('UNKNOWN' !== $this->getName()) { + if ('UNKNOWN' !== $this->getVersion()) { + return sprintf('%s %s', $this->getName(), $this->getVersion()); + } + + return $this->getName(); + } + + return 'Console Tool'; + } + + /** + * Registers a new command. + * + * @param string $name The command name + * + * @return Command The newly created command + */ + public function register($name) + { + return $this->add(new Command($name)); + } + + /** + * Adds an array of command objects. + * + * If a Command is not enabled it will not be added. + * + * @param Command[] $commands An array of commands + */ + public function addCommands(array $commands) + { + foreach ($commands as $command) { + $this->add($command); + } + } + + /** + * Adds a command object. + * + * If a command with the same name already exists, it will be overridden. + * If the command is not enabled it will not be added. + * + * @param Command $command A Command object + * + * @return Command|null The registered command if enabled or null + */ + public function add(Command $command) + { + $command->setApplication($this); + + if (!$command->isEnabled()) { + $command->setApplication(null); + + return; + } + + if (null === $command->getDefinition()) { + throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))); + } + + $this->commands[$command->getName()] = $command; + + foreach ($command->getAliases() as $alias) { + $this->commands[$alias] = $command; + } + + return $command; + } + + /** + * Returns a registered command by name or alias. + * + * @param string $name The command name or alias + * + * @return Command A Command object + * + * @throws CommandNotFoundException When given command name does not exist + */ + public function get($name) + { + if (!isset($this->commands[$name])) { + throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); + } + + $command = $this->commands[$name]; + + if ($this->wantHelps) { + $this->wantHelps = false; + + $helpCommand = $this->get('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * Returns true if the command exists, false otherwise. + * + * @param string $name The command name or alias + * + * @return bool true if the command exists, false otherwise + */ + public function has($name) + { + return isset($this->commands[$name]); + } + + /** + * Returns an array of all unique namespaces used by currently registered commands. + * + * It does not return the global namespace which always exists. + * + * @return string[] An array of namespaces + */ + public function getNamespaces() + { + $namespaces = array(); + foreach ($this->all() as $command) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); + + foreach ($command->getAliases() as $alias) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); + } + } + + return array_values(array_unique(array_filter($namespaces))); + } + + /** + * Finds a registered namespace by a name or an abbreviation. + * + * @param string $namespace A namespace or abbreviation to search for + * + * @return string A registered namespace + * + * @throws CommandNotFoundException When namespace is incorrect or ambiguous + */ + public function findNamespace($namespace) + { + $allNamespaces = $this->getNamespaces(); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace); + $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); + + if (empty($namespaces)) { + $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); + + if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + + $message .= implode("\n ", $alternatives); + } + + throw new CommandNotFoundException($message, $alternatives); + } + + $exact = in_array($namespace, $namespaces, true); + if (count($namespaces) > 1 && !$exact) { + throw new CommandNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces)); + } + + return $exact ? $namespace : reset($namespaces); + } + + /** + * Finds a command by name or alias. + * + * Contrary to get, this command tries to find the best + * match if you give it an abbreviation of a name or alias. + * + * @param string $name A command name or a command alias + * + * @return Command A Command instance + * + * @throws CommandNotFoundException When command name is incorrect or ambiguous + */ + public function find($name) + { + $allCommands = array_keys($this->commands); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name); + $commands = preg_grep('{^'.$expr.'}', $allCommands); + + if (empty($commands) || count(preg_grep('{^'.$expr.'$}', $commands)) < 1) { + if (false !== $pos = strrpos($name, ':')) { + // check if a namespace exists and contains commands + $this->findNamespace(substr($name, 0, $pos)); + } + + $message = sprintf('Command "%s" is not defined.', $name); + + if ($alternatives = $this->findAlternatives($name, $allCommands)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new CommandNotFoundException($message, $alternatives); + } + + // filter out aliases for commands which are already on the list + if (count($commands) > 1) { + $commandList = $this->commands; + $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) { + $commandName = $commandList[$nameOrAlias]->getName(); + + return $commandName === $nameOrAlias || !in_array($commandName, $commands); + }); + } + + $exact = in_array($name, $commands, true); + if (count($commands) > 1 && !$exact) { + $usableWidth = $this->terminal->getWidth() - 10; + $abbrevs = array_values($commands); + $maxLen = 0; + foreach ($abbrevs as $abbrev) { + $maxLen = max(Helper::strlen($abbrev), $maxLen); + } + $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen) { + $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription(); + + return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev; + }, array_values($commands)); + $suggestions = $this->getAbbreviationSuggestions($abbrevs); + + throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $name, $suggestions), array_values($commands)); + } + + return $this->get($exact ? $name : reset($commands)); + } + + /** + * Gets the commands (registered in the given namespace if provided). + * + * The array keys are the full names and the values the command instances. + * + * @param string $namespace A namespace name + * + * @return Command[] An array of Command instances + */ + public function all($namespace = null) + { + if (null === $namespace) { + return $this->commands; + } + + $commands = array(); + foreach ($this->commands as $name => $command) { + if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) { + $commands[$name] = $command; + } + } + + return $commands; + } + + /** + * Returns an array of possible abbreviations given a set of names. + * + * @param array $names An array of names + * + * @return array An array of abbreviations + */ + public static function getAbbreviations($names) + { + $abbrevs = array(); + foreach ($names as $name) { + for ($len = strlen($name); $len > 0; --$len) { + $abbrev = substr($name, 0, $len); + $abbrevs[$abbrev][] = $name; + } + } + + return $abbrevs; + } + + /** + * Renders a caught exception. + * + * @param \Exception $e An exception instance + * @param OutputInterface $output An OutputInterface instance + */ + public function renderException(\Exception $e, OutputInterface $output) + { + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + + do { + $title = sprintf( + ' [%s%s] ', + get_class($e), + $output->isVerbose() && 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '' + ); + + $len = Helper::strlen($title); + + $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX; + // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327 + if (defined('HHVM_VERSION') && $width > 1 << 31) { + $width = 1 << 31; + } + $lines = array(); + foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { + // pre-format lines to get the right string length + $lineLength = Helper::strlen($line) + 4; + $lines[] = array($line, $lineLength); + + $len = max($lineLength, $len); + } + } + + $messages = array(); + $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); + $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - Helper::strlen($title)))); + foreach ($lines as $line) { + $messages[] = sprintf(' %s %s', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1])); + } + $messages[] = $emptyLine; + $messages[] = ''; + + $output->writeln($messages, OutputInterface::VERBOSITY_QUIET); + + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $output->writeln('Exception trace:', OutputInterface::VERBOSITY_QUIET); + + // exception related properties + $trace = $e->getTrace(); + array_unshift($trace, array( + 'function' => '', + 'file' => $e->getFile() !== null ? $e->getFile() : 'n/a', + 'line' => $e->getLine() !== null ? $e->getLine() : 'n/a', + 'args' => array(), + )); + + for ($i = 0, $count = count($trace); $i < $count; ++$i) { + $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; + $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; + $function = $trace[$i]['function']; + $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; + $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; + + $output->writeln(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line), OutputInterface::VERBOSITY_QUIET); + } + + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + } + } while ($e = $e->getPrevious()); + + if (null !== $this->runningCommand) { + $output->writeln(sprintf('%s', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET); + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + } + } + + /** + * Tries to figure out the terminal width in which this application runs. + * + * @return int|null + * + * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead. + */ + protected function getTerminalWidth() + { + @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->terminal->getWidth(); + } + + /** + * Tries to figure out the terminal height in which this application runs. + * + * @return int|null + * + * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead. + */ + protected function getTerminalHeight() + { + @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->terminal->getHeight(); + } + + /** + * Tries to figure out the terminal dimensions based on the current environment. + * + * @return array Array containing width and height + * + * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead. + */ + public function getTerminalDimensions() + { + @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED); + + return array($this->terminal->getWidth(), $this->terminal->getHeight()); + } + + /** + * Sets terminal dimensions. + * + * Can be useful to force terminal dimensions for functional tests. + * + * @param int $width The width + * @param int $height The height + * + * @return $this + * + * @deprecated since version 3.2, to be removed in 4.0. Set the COLUMNS and LINES env vars instead. + */ + public function setTerminalDimensions($width, $height) + { + @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Set the COLUMNS and LINES env vars instead.', __METHOD__), E_USER_DEPRECATED); + + putenv('COLUMNS='.$width); + putenv('LINES='.$height); + + return $this; + } + + /** + * Configures the input and output instances based on the user arguments and options. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + */ + protected function configureIO(InputInterface $input, OutputInterface $output) + { + if (true === $input->hasParameterOption(array('--ansi'), true)) { + $output->setDecorated(true); + } elseif (true === $input->hasParameterOption(array('--no-ansi'), true)) { + $output->setDecorated(false); + } + + if (true === $input->hasParameterOption(array('--no-interaction', '-n'), true)) { + $input->setInteractive(false); + } elseif (function_exists('posix_isatty')) { + $inputStream = null; + + if ($input instanceof StreamableInputInterface) { + $inputStream = $input->getStream(); + } + + // This check ensures that calling QuestionHelper::setInputStream() works + // To be removed in 4.0 (in the same time as QuestionHelper::setInputStream) + if (!$inputStream && $this->getHelperSet()->has('question')) { + $inputStream = $this->getHelperSet()->get('question')->getInputStream(false); + } + + if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) { + $input->setInteractive(false); + } + } + + if (true === $input->hasParameterOption(array('--quiet', '-q'), true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); + $input->setInteractive(false); + } else { + if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || $input->getParameterOption('--verbose', false, true) === 3) { + $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); + } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || $input->getParameterOption('--verbose', false, true) === 2) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); + } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + } + } + } + + /** + * Runs the current command. + * + * If an event dispatcher has been attached to the application, + * events are also dispatched during the life-cycle of the command. + * + * @param Command $command A Command instance + * @param InputInterface $input An Input instance + * @param OutputInterface $output An Output instance + * + * @return int 0 if everything went fine, or an error code + */ + protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) + { + foreach ($command->getHelperSet() as $helper) { + if ($helper instanceof InputAwareInterface) { + $helper->setInput($input); + } + } + + if (null === $this->dispatcher) { + return $command->run($input, $output); + } + + // bind before the console.command event, so the listeners have access to input options/arguments + try { + $command->mergeApplicationDefinition(); + $input->bind($command->getDefinition()); + } catch (ExceptionInterface $e) { + // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition + } + + $event = new ConsoleCommandEvent($command, $input, $output); + $e = null; + + try { + $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event); + + if ($event->commandShouldRun()) { + $exitCode = $command->run($input, $output); + } else { + $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; + } + } catch (\Exception $e) { + } catch (\Throwable $e) { + } + if (null !== $e) { + if ($this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) { + $x = $e instanceof \Exception ? $e : new FatalThrowableError($e); + $event = new ConsoleExceptionEvent($command, $input, $output, $x, $x->getCode()); + $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event); + + if ($x !== $event->getException()) { + $e = $event->getException(); + } + } + $event = new ConsoleErrorEvent($input, $output, $e, $command); + $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event); + $e = $event->getError(); + + if (0 === $exitCode = $event->getExitCode()) { + $e = null; + } + } + + $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); + $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); + + if (null !== $e) { + throw $e; + } + + return $event->getExitCode(); + } + + /** + * Gets the name of the command based on input. + * + * @param InputInterface $input The input interface + * + * @return string The command name + */ + protected function getCommandName(InputInterface $input) + { + return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument(); + } + + /** + * Gets the default input definition. + * + * @return InputDefinition An InputDefinition instance + */ + protected function getDefaultInputDefinition() + { + return new InputDefinition(array( + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), + new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), + new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), + new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), + new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), + new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), + new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), + )); + } + + /** + * Gets the default commands that should always be available. + * + * @return Command[] An array of default Command instances + */ + protected function getDefaultCommands() + { + return array(new HelpCommand(), new ListCommand()); + } + + /** + * Gets the default helper set with the helpers that should always be available. + * + * @return HelperSet A HelperSet instance + */ + protected function getDefaultHelperSet() + { + return new HelperSet(array( + new FormatterHelper(), + new DebugFormatterHelper(), + new ProcessHelper(), + new QuestionHelper(), + )); + } + + /** + * Returns abbreviated suggestions in string format. + * + * @param array $abbrevs Abbreviated suggestions to convert + * + * @return string A formatted string of abbreviated suggestions + */ + private function getAbbreviationSuggestions($abbrevs) + { + return ' '.implode("\n ", $abbrevs); + } + + /** + * Returns the namespace part of the command name. + * + * This method is not part of public API and should not be used directly. + * + * @param string $name The full name of the command + * @param string $limit The maximum number of parts of the namespace + * + * @return string The namespace of the command + */ + public function extractNamespace($name, $limit = null) + { + $parts = explode(':', $name); + array_pop($parts); + + return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit)); + } + + /** + * Finds alternative of $name among $collection, + * if nothing is found in $collection, try in $abbrevs. + * + * @param string $name The string + * @param array|\Traversable $collection The collection + * + * @return string[] A sorted array of similar string + */ + private function findAlternatives($name, $collection) + { + $threshold = 1e3; + $alternatives = array(); + + $collectionParts = array(); + foreach ($collection as $item) { + $collectionParts[$item] = explode(':', $item); + } + + foreach (explode(':', $name) as $i => $subname) { + foreach ($collectionParts as $collectionName => $parts) { + $exists = isset($alternatives[$collectionName]); + if (!isset($parts[$i]) && $exists) { + $alternatives[$collectionName] += $threshold; + continue; + } elseif (!isset($parts[$i])) { + continue; + } + + $lev = levenshtein($subname, $parts[$i]); + if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) { + $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; + } elseif ($exists) { + $alternatives[$collectionName] += $threshold; + } + } + } + + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); + ksort($alternatives, SORT_NATURAL | SORT_FLAG_CASE); + + return array_keys($alternatives); + } + + /** + * Sets the default Command name. + * + * @param string $commandName The Command name + * @param bool $isSingleCommand Set to true if there is only one command in this application + * + * @return self + */ + public function setDefaultCommand($commandName, $isSingleCommand = false) + { + $this->defaultCommand = $commandName; + + if ($isSingleCommand) { + // Ensure the command exist + $this->find($commandName); + + $this->singleCommand = true; + } + + return $this; + } + + private function splitStringByWidth($string, $width) + { + // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly. + // additionally, array_slice() is not enough as some character has doubled width. + // we need a function to split string not by character count but by string width + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = array(); + $line = ''; + foreach (preg_split('//u', $utf8String) as $char) { + // test if $char could be appended to current line + if (mb_strwidth($line.$char, 'utf8') <= $width) { + $line .= $char; + continue; + } + // if not, push current line to array and make new line + $lines[] = str_pad($line, $width); + $line = $char; + } + if ('' !== $line) { + $lines[] = count($lines) ? str_pad($line, $width) : $line; + } + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } + + /** + * Returns all namespaces of the command name. + * + * @param string $name The full name of the command + * + * @return string[] The namespaces of the command + */ + private function extractAllNamespaces($name) + { + // -1 as third argument is needed to skip the command short name when exploding + $parts = explode(':', $name, -1); + $namespaces = array(); + + foreach ($parts as $part) { + if (count($namespaces)) { + $namespaces[] = end($namespaces).':'.$part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } +} diff --git a/vendor/symfony/console/CHANGELOG.md b/vendor/symfony/console/CHANGELOG.md new file mode 100644 index 00000000..f8539491 --- /dev/null +++ b/vendor/symfony/console/CHANGELOG.md @@ -0,0 +1,99 @@ +CHANGELOG +========= + +3.3.0 +----- + +* added `ExceptionListener` +* added `AddConsoleCommandPass` (originally in FrameworkBundle) +* [BC BREAK] `Input::getOption()` no longer returns the default value for options + with value optional explicitly passed empty +* added console.error event to catch exceptions thrown by other listeners +* deprecated console.exception event in favor of console.error +* added ability to handle `CommandNotFoundException` through the + `console.error` event + +3.2.0 +------ + +* added `setInputs()` method to CommandTester for ease testing of commands expecting inputs +* added `setStream()` and `getStream()` methods to Input (implement StreamableInputInterface) +* added StreamableInputInterface +* added LockableTrait + +3.1.0 +----- + + * added truncate method to FormatterHelper + * added setColumnWidth(s) method to Table + +2.8.3 +----- + + * remove readline support from the question helper as it caused issues + +2.8.0 +----- + + * use readline for user input in the question helper when available to allow + the use of arrow keys + +2.6.0 +----- + + * added a Process helper + * added a DebugFormatter helper + +2.5.0 +----- + + * deprecated the dialog helper (use the question helper instead) + * deprecated TableHelper in favor of Table + * deprecated ProgressHelper in favor of ProgressBar + * added ConsoleLogger + * added a question helper + * added a way to set the process name of a command + * added a way to set a default command instead of `ListCommand` + +2.4.0 +----- + + * added a way to force terminal dimensions + * added a convenient method to detect verbosity level + * [BC BREAK] made descriptors use output instead of returning a string + +2.3.0 +----- + + * added multiselect support to the select dialog helper + * added Table Helper for tabular data rendering + * added support for events in `Application` + * added a way to normalize EOLs in `ApplicationTester::getDisplay()` and `CommandTester::getDisplay()` + * added a way to set the progress bar progress via the `setCurrent` method + * added support for multiple InputOption shortcuts, written as `'-a|-b|-c'` + * added two additional verbosity levels, VERBOSITY_VERY_VERBOSE and VERBOSITY_DEBUG + +2.2.0 +----- + + * added support for colorization on Windows via ConEmu + * add a method to Dialog Helper to ask for a question and hide the response + * added support for interactive selections in console (DialogHelper::select()) + * added support for autocompletion as you type in Dialog Helper + +2.1.0 +----- + + * added ConsoleOutputInterface + * added the possibility to disable a command (Command::isEnabled()) + * added suggestions when a command does not exist + * added a --raw option to the list command + * added support for STDERR in the console output class (errors are now sent + to STDERR) + * made the defaults (helper set, commands, input definition) in Application + more easily customizable + * added support for the shell even if readline is not available + * added support for process isolation in Symfony shell via + `--process-isolation` switch + * added support for `--`, which disables options parsing after that point + (tokens will be parsed as arguments) diff --git a/vendor/symfony/console/Command/Command.php b/vendor/symfony/console/Command/Command.php new file mode 100644 index 00000000..08a74c3b --- /dev/null +++ b/vendor/symfony/console/Command/Command.php @@ -0,0 +1,669 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Base class for all commands. + * + * @author Fabien Potencier + */ +class Command +{ + private $application; + private $name; + private $processTitle; + private $aliases = array(); + private $definition; + private $hidden = false; + private $help; + private $description; + private $ignoreValidationErrors = false; + private $applicationDefinitionMerged = false; + private $applicationDefinitionMergedWithArgs = false; + private $code; + private $synopsis = array(); + private $usages = array(); + private $helperSet; + + /** + * Constructor. + * + * @param string|null $name The name of the command; passing null means it must be set in configure() + * + * @throws LogicException When the command name is empty + */ + public function __construct($name = null) + { + $this->definition = new InputDefinition(); + + if (null !== $name) { + $this->setName($name); + } + + $this->configure(); + + if (!$this->name) { + throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this))); + } + } + + /** + * Ignores validation errors. + * + * This is mainly useful for the help command. + */ + public function ignoreValidationErrors() + { + $this->ignoreValidationErrors = true; + } + + /** + * Sets the application instance for this command. + * + * @param Application $application An Application instance + */ + public function setApplication(Application $application = null) + { + $this->application = $application; + if ($application) { + $this->setHelperSet($application->getHelperSet()); + } else { + $this->helperSet = null; + } + } + + /** + * Sets the helper set. + * + * @param HelperSet $helperSet A HelperSet instance + */ + public function setHelperSet(HelperSet $helperSet) + { + $this->helperSet = $helperSet; + } + + /** + * Gets the helper set. + * + * @return HelperSet A HelperSet instance + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Gets the application instance for this command. + * + * @return Application An Application instance + */ + public function getApplication() + { + return $this->application; + } + + /** + * Checks whether the command is enabled or not in the current environment. + * + * Override this to check for x or y and return false if the command can not + * run properly under the current conditions. + * + * @return bool + */ + public function isEnabled() + { + return true; + } + + /** + * Configures the current command. + */ + protected function configure() + { + } + + /** + * Executes the current command. + * + * This method is not abstract because you can use this class + * as a concrete class. In this case, instead of defining the + * execute() method, you set the code to execute by passing + * a Closure to the setCode() method. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return null|int null or 0 if everything went fine, or an error code + * + * @throws LogicException When this abstract method is not implemented + * + * @see setCode() + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + throw new LogicException('You must override the execute() method in the concrete command class.'); + } + + /** + * Interacts with the user. + * + * This method is executed before the InputDefinition is validated. + * This means that this is the only place where the command can + * interactively ask for values of missing required arguments. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + */ + protected function interact(InputInterface $input, OutputInterface $output) + { + } + + /** + * Initializes the command just after the input has been validated. + * + * This is mainly useful when a lot of commands extends one main command + * where some things need to be initialized based on the input arguments and options. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + */ + protected function initialize(InputInterface $input, OutputInterface $output) + { + } + + /** + * Runs the command. + * + * The code to execute is either defined directly with the + * setCode() method or by overriding the execute() method + * in a sub-class. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return int The command exit code + * + * @throws \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}. + * + * @see setCode() + * @see execute() + */ + public function run(InputInterface $input, OutputInterface $output) + { + // force the creation of the synopsis before the merge with the app definition + $this->getSynopsis(true); + $this->getSynopsis(false); + + // add the application arguments and options + $this->mergeApplicationDefinition(); + + // bind the input against the command specific arguments/options + try { + $input->bind($this->definition); + } catch (ExceptionInterface $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + if (null !== $this->processTitle) { + if (function_exists('cli_set_process_title')) { + if (false === @cli_set_process_title($this->processTitle)) { + if ('Darwin' === PHP_OS) { + $output->writeln('Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.'); + } else { + $error = error_get_last(); + trigger_error($error['message'], E_USER_WARNING); + } + } + } elseif (function_exists('setproctitle')) { + setproctitle($this->processTitle); + } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { + $output->writeln('Install the proctitle PECL to be able to change the process title.'); + } + } + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + // The command name argument is often omitted when a command is executed directly with its run() method. + // It would fail the validation if we didn't make sure the command argument is present, + // since it's required by the application. + if ($input->hasArgument('command') && null === $input->getArgument('command')) { + $input->setArgument('command', $this->getName()); + } + + $input->validate(); + + if ($this->code) { + $statusCode = call_user_func($this->code, $input, $output); + } else { + $statusCode = $this->execute($input, $output); + } + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * Sets the code to execute when running this command. + * + * If this method is used, it overrides the code defined + * in the execute() method. + * + * @param callable $code A callable(InputInterface $input, OutputInterface $output) + * + * @return $this + * + * @throws InvalidArgumentException + * + * @see execute() + */ + public function setCode(callable $code) + { + if ($code instanceof \Closure) { + $r = new \ReflectionFunction($code); + if (null === $r->getClosureThis()) { + if (\PHP_VERSION_ID < 70000) { + // Bug in PHP5: https://bugs.php.net/bug.php?id=64761 + // This means that we cannot bind static closures and therefore we must + // ignore any errors here. There is no way to test if the closure is + // bindable. + $code = @\Closure::bind($code, $this); + } else { + $code = \Closure::bind($code, $this); + } + } + } + + $this->code = $code; + + return $this; + } + + /** + * Merges the application definition with the command definition. + * + * This method is not part of public API and should not be used directly. + * + * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments + */ + public function mergeApplicationDefinition($mergeArgs = true) + { + if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) { + return; + } + + $this->definition->addOptions($this->application->getDefinition()->getOptions()); + + if ($mergeArgs) { + $currentArguments = $this->definition->getArguments(); + $this->definition->setArguments($this->application->getDefinition()->getArguments()); + $this->definition->addArguments($currentArguments); + } + + $this->applicationDefinitionMerged = true; + if ($mergeArgs) { + $this->applicationDefinitionMergedWithArgs = true; + } + } + + /** + * Sets an array of argument and option instances. + * + * @param array|InputDefinition $definition An array of argument and option instances or a definition instance + * + * @return $this + */ + public function setDefinition($definition) + { + if ($definition instanceof InputDefinition) { + $this->definition = $definition; + } else { + $this->definition->setDefinition($definition); + } + + $this->applicationDefinitionMerged = false; + + return $this; + } + + /** + * Gets the InputDefinition attached to this Command. + * + * @return InputDefinition An InputDefinition instance + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * Gets the InputDefinition to be used to create representations of this Command. + * + * Can be overridden to provide the original command representation when it would otherwise + * be changed by merging with the application InputDefinition. + * + * This method is not part of public API and should not be used directly. + * + * @return InputDefinition An InputDefinition instance + */ + public function getNativeDefinition() + { + return $this->getDefinition(); + } + + /** + * Adds an argument. + * + * @param string $name The argument name + * @param int $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL + * @param string $description A description text + * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) + * + * @return $this + */ + public function addArgument($name, $mode = null, $description = '', $default = null) + { + $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); + + return $this; + } + + /** + * Adds an option. + * + * @param string $name The option name + * @param string $shortcut The shortcut (can be null) + * @param int $mode The option mode: One of the InputOption::VALUE_* constants + * @param string $description A description text + * @param mixed $default The default value (must be null for InputOption::VALUE_NONE) + * + * @return $this + */ + public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); + + return $this; + } + + /** + * Sets the name of the command. + * + * This method can set both the namespace and the name if + * you separate them by a colon (:) + * + * $command->setName('foo:bar'); + * + * @param string $name The command name + * + * @return $this + * + * @throws InvalidArgumentException When the name is invalid + */ + public function setName($name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * Sets the process title of the command. + * + * This feature should be used only when creating a long process command, + * like a daemon. + * + * PHP 5.5+ or the proctitle PECL library is required + * + * @param string $title The process title + * + * @return $this + */ + public function setProcessTitle($title) + { + $this->processTitle = $title; + + return $this; + } + + /** + * Returns the command name. + * + * @return string The command name + */ + public function getName() + { + return $this->name; + } + + /** + * @param bool $hidden Whether or not the command should be hidden from the list of commands + * + * @return Command The current instance + */ + public function setHidden($hidden) + { + $this->hidden = (bool) $hidden; + + return $this; + } + + /** + * @return bool Whether the command should be publicly shown or not. + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * Sets the description for the command. + * + * @param string $description The description for the command + * + * @return $this + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * Returns the description for the command. + * + * @return string The description for the command + */ + public function getDescription() + { + return $this->description; + } + + /** + * Sets the help for the command. + * + * @param string $help The help for the command + * + * @return $this + */ + public function setHelp($help) + { + $this->help = $help; + + return $this; + } + + /** + * Returns the help for the command. + * + * @return string The help for the command + */ + public function getHelp() + { + return $this->help; + } + + /** + * Returns the processed help for the command replacing the %command.name% and + * %command.full_name% patterns with the real values dynamically. + * + * @return string The processed help for the command + */ + public function getProcessedHelp() + { + $name = $this->name; + + $placeholders = array( + '%command.name%', + '%command.full_name%', + ); + $replacements = array( + $name, + $_SERVER['PHP_SELF'].' '.$name, + ); + + return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription()); + } + + /** + * Sets the aliases for the command. + * + * @param string[] $aliases An array of aliases for the command + * + * @return $this + * + * @throws InvalidArgumentException When an alias is invalid + */ + public function setAliases($aliases) + { + if (!is_array($aliases) && !$aliases instanceof \Traversable) { + throw new InvalidArgumentException('$aliases must be an array or an instance of \Traversable'); + } + + foreach ($aliases as $alias) { + $this->validateName($alias); + } + + $this->aliases = $aliases; + + return $this; + } + + /** + * Returns the aliases for the command. + * + * @return array An array of aliases for the command + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * Returns the synopsis for the command. + * + * @param bool $short Whether to show the short version of the synopsis (with options folded) or not + * + * @return string The synopsis + */ + public function getSynopsis($short = false) + { + $key = $short ? 'short' : 'long'; + + if (!isset($this->synopsis[$key])) { + $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); + } + + return $this->synopsis[$key]; + } + + /** + * Add a command usage example. + * + * @param string $usage The usage, it'll be prefixed with the command name + * + * @return $this + */ + public function addUsage($usage) + { + if (0 !== strpos($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * Returns alternative usages of the command. + * + * @return array + */ + public function getUsages() + { + return $this->usages; + } + + /** + * Gets a helper instance by name. + * + * @param string $name The helper name + * + * @return mixed The helper value + * + * @throws LogicException if no HelperSet is defined + * @throws InvalidArgumentException if the helper is not defined + */ + public function getHelper($name) + { + if (null === $this->helperSet) { + throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name)); + } + + return $this->helperSet->get($name); + } + + /** + * Validates a command name. + * + * It must be non-empty and parts can optionally be separated by ":". + * + * @param string $name + * + * @throws InvalidArgumentException When the name is invalid + */ + private function validateName($name) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } +} diff --git a/vendor/symfony/console/Command/HelpCommand.php b/vendor/symfony/console/Command/HelpCommand.php new file mode 100644 index 00000000..b8fd911a --- /dev/null +++ b/vendor/symfony/console/Command/HelpCommand.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\Console\Command; + +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * HelpCommand displays the help for a given command. + * + * @author Fabien Potencier + */ +class HelpCommand extends Command +{ + private $command; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->ignoreValidationErrors(); + + $this + ->setName('help') + ->setDefinition(array( + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), + )) + ->setDescription('Displays help for a command') + ->setHelp(<<<'EOF' +The %command.name% command displays help for a given command: + + php %command.full_name% list + +You can also output the help in other formats by using the --format option: + + php %command.full_name% --format=xml list + +To display the list of available commands, please use the list command. +EOF + ) + ; + } + + /** + * Sets the command. + * + * @param Command $command The command to set + */ + public function setCommand(Command $command) + { + $this->command = $command; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if (null === $this->command) { + $this->command = $this->getApplication()->find($input->getArgument('command_name')); + } + + $helper = new DescriptorHelper(); + $helper->describe($output, $this->command, array( + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + )); + + $this->command = null; + } +} diff --git a/vendor/symfony/console/Command/ListCommand.php b/vendor/symfony/console/Command/ListCommand.php new file mode 100644 index 00000000..179ddea5 --- /dev/null +++ b/vendor/symfony/console/Command/ListCommand.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputDefinition; + +/** + * ListCommand displays the list of all available commands for the application. + * + * @author Fabien Potencier + */ +class ListCommand extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('list') + ->setDefinition($this->createDefinition()) + ->setDescription('Lists commands') + ->setHelp(<<<'EOF' +The %command.name% command lists all commands: + + php %command.full_name% + +You can also display the commands for a specific namespace: + + php %command.full_name% test + +You can also output the information in other formats by using the --format option: + + php %command.full_name% --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + php %command.full_name% --raw +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + public function getNativeDefinition() + { + return $this->createDefinition(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $helper = new DescriptorHelper(); + $helper->describe($output, $this->getApplication(), array( + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'namespace' => $input->getArgument('namespace'), + )); + } + + /** + * {@inheritdoc} + */ + private function createDefinition() + { + return new InputDefinition(array( + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + )); + } +} diff --git a/vendor/symfony/console/Command/LockableTrait.php b/vendor/symfony/console/Command/LockableTrait.php new file mode 100644 index 00000000..95597705 --- /dev/null +++ b/vendor/symfony/console/Command/LockableTrait.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\Console\Command; + +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Filesystem\LockHandler; + +/** + * Basic lock feature for commands. + * + * @author Geoffrey Brier + */ +trait LockableTrait +{ + private $lockHandler; + + /** + * Locks a command. + * + * @return bool + */ + private function lock($name = null, $blocking = false) + { + if (!class_exists(LockHandler::class)) { + throw new RuntimeException('To enable the locking feature you must install the symfony/filesystem component.'); + } + + if (null !== $this->lockHandler) { + throw new LogicException('A lock is already in place.'); + } + + $this->lockHandler = new LockHandler($name ?: $this->getName()); + + if (!$this->lockHandler->lock($blocking)) { + $this->lockHandler = null; + + return false; + } + + return true; + } + + /** + * Releases the command lock if there is one. + */ + private function release() + { + if ($this->lockHandler) { + $this->lockHandler->release(); + $this->lockHandler = null; + } + } +} diff --git a/vendor/symfony/console/ConsoleEvents.php b/vendor/symfony/console/ConsoleEvents.php new file mode 100644 index 00000000..7f7d4a3f --- /dev/null +++ b/vendor/symfony/console/ConsoleEvents.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\Console; + +/** + * Contains all events dispatched by an Application. + * + * @author Francesco Levorato + */ +final class ConsoleEvents +{ + /** + * The COMMAND event allows you to attach listeners before any command is + * executed by the console. It also allows you to modify the command, input and output + * before they are handled to the command. + * + * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent") + * + * @var string + */ + const COMMAND = 'console.command'; + + /** + * The TERMINATE event allows you to attach listeners after a command is + * executed by the console. + * + * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent") + * + * @var string + */ + const TERMINATE = 'console.terminate'; + + /** + * The EXCEPTION event occurs when an uncaught exception appears + * while executing Command#run(). + * + * This event allows you to deal with the exception or + * to modify the thrown exception. + * + * @Event("Symfony\Component\Console\Event\ConsoleExceptionEvent") + * + * @var string + * + * @deprecated The console.exception event is deprecated since version 3.3 and will be removed in 4.0. Use the console.error event instead. + */ + const EXCEPTION = 'console.exception'; + + /** + * The ERROR event occurs when an uncaught exception or error appears. + * + * This event allows you to deal with the exception/error or + * to modify the thrown exception. + * + * @Event("Symfony\Component\Console\Event\ConsoleErrorEvent") + * + * @var string + */ + const ERROR = 'console.error'; +} diff --git a/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php b/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php new file mode 100644 index 00000000..d0626be1 --- /dev/null +++ b/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.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\Console\DependencyInjection; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * Registers console commands. + * + * @author Grégoire Pineau + */ +class AddConsoleCommandPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + $commandServices = $container->findTaggedServiceIds('console.command', true); + $serviceIds = array(); + + foreach ($commandServices as $id => $tags) { + $definition = $container->getDefinition($id); + $class = $container->getParameterBag()->resolveValue($definition->getClass()); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(Command::class)) { + throw new InvalidArgumentException(sprintf('The service "%s" tagged "console.command" must be a subclass of "%s".', $id, Command::class)); + } + + $commandId = 'console.command.'.strtolower(str_replace('\\', '_', $class)); + if ($container->hasAlias($commandId) || isset($serviceIds[$commandId])) { + $commandId = $commandId.'_'.$id; + } + if (!$definition->isPublic()) { + $container->setAlias($commandId, $id); + $id = $commandId; + } + + $serviceIds[$commandId] = $id; + } + + $container->setParameter('console.command.ids', $serviceIds); + } +} diff --git a/vendor/symfony/console/Descriptor/ApplicationDescription.php b/vendor/symfony/console/Descriptor/ApplicationDescription.php new file mode 100644 index 00000000..a9740fe0 --- /dev/null +++ b/vendor/symfony/console/Descriptor/ApplicationDescription.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\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * @author Jean-François Simon + * + * @internal + */ +class ApplicationDescription +{ + const GLOBAL_NAMESPACE = '_global'; + + /** + * @var Application + */ + private $application; + + /** + * @var null|string + */ + private $namespace; + + /** + * @var array + */ + private $namespaces; + + /** + * @var Command[] + */ + private $commands; + + /** + * @var Command[] + */ + private $aliases; + + /** + * @var bool + */ + private $showHidden; + + /** + * Constructor. + * + * @param Application $application + * @param string|null $namespace + * @param bool $showHidden + */ + public function __construct(Application $application, $namespace = null, $showHidden = false) + { + $this->application = $application; + $this->namespace = $namespace; + $this->showHidden = $showHidden; + } + + /** + * @return array + */ + public function getNamespaces() + { + if (null === $this->namespaces) { + $this->inspectApplication(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands() + { + if (null === $this->commands) { + $this->inspectApplication(); + } + + return $this->commands; + } + + /** + * @param string $name + * + * @return Command + * + * @throws CommandNotFoundException + */ + public function getCommand($name) + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new CommandNotFoundException(sprintf('Command %s does not exist.', $name)); + } + + return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; + } + + private function inspectApplication() + { + $this->commands = array(); + $this->namespaces = array(); + + $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null); + foreach ($this->sortCommands($all) as $namespace => $commands) { + $names = array(); + + /** @var Command $command */ + foreach ($commands as $name => $command) { + if (!$command->getName() || (!$this->showHidden && $command->isHidden())) { + continue; + } + + if ($command->getName() === $name) { + $this->commands[$name] = $command; + } else { + $this->aliases[$name] = $command; + } + + $names[] = $name; + } + + $this->namespaces[$namespace] = array('id' => $namespace, 'commands' => $names); + } + } + + /** + * @param array $commands + * + * @return array + */ + private function sortCommands(array $commands) + { + $namespacedCommands = array(); + $globalCommands = array(); + foreach ($commands as $name => $command) { + $key = $this->application->extractNamespace($name, 1); + if (!$key) { + $globalCommands['_global'][$name] = $command; + } else { + $namespacedCommands[$key][$name] = $command; + } + } + ksort($namespacedCommands); + $namespacedCommands = array_merge($globalCommands, $namespacedCommands); + + foreach ($namespacedCommands as &$commandsSet) { + ksort($commandsSet); + } + // unset reference to keep scope clear + unset($commandsSet); + + return $namespacedCommands; + } +} diff --git a/vendor/symfony/console/Descriptor/Descriptor.php b/vendor/symfony/console/Descriptor/Descriptor.php new file mode 100644 index 00000000..50dd86ce --- /dev/null +++ b/vendor/symfony/console/Descriptor/Descriptor.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Jean-François Simon + * + * @internal + */ +abstract class Descriptor implements DescriptorInterface +{ + /** + * @var OutputInterface + */ + protected $output; + + /** + * {@inheritdoc} + */ + public function describe(OutputInterface $output, $object, array $options = array()) + { + $this->output = $output; + + switch (true) { + case $object instanceof InputArgument: + $this->describeInputArgument($object, $options); + break; + case $object instanceof InputOption: + $this->describeInputOption($object, $options); + break; + case $object instanceof InputDefinition: + $this->describeInputDefinition($object, $options); + break; + case $object instanceof Command: + $this->describeCommand($object, $options); + break; + case $object instanceof Application: + $this->describeApplication($object, $options); + break; + default: + throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object))); + } + } + + /** + * Writes content to output. + * + * @param string $content + * @param bool $decorated + */ + protected function write($content, $decorated = false) + { + $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); + } + + /** + * Describes an InputArgument instance. + * + * @param InputArgument $argument + * @param array $options + * + * @return string|mixed + */ + abstract protected function describeInputArgument(InputArgument $argument, array $options = array()); + + /** + * Describes an InputOption instance. + * + * @param InputOption $option + * @param array $options + * + * @return string|mixed + */ + abstract protected function describeInputOption(InputOption $option, array $options = array()); + + /** + * Describes an InputDefinition instance. + * + * @param InputDefinition $definition + * @param array $options + * + * @return string|mixed + */ + abstract protected function describeInputDefinition(InputDefinition $definition, array $options = array()); + + /** + * Describes a Command instance. + * + * @param Command $command + * @param array $options + * + * @return string|mixed + */ + abstract protected function describeCommand(Command $command, array $options = array()); + + /** + * Describes an Application instance. + * + * @param Application $application + * @param array $options + * + * @return string|mixed + */ + abstract protected function describeApplication(Application $application, array $options = array()); +} diff --git a/vendor/symfony/console/Descriptor/DescriptorInterface.php b/vendor/symfony/console/Descriptor/DescriptorInterface.php new file mode 100644 index 00000000..3929b6d9 --- /dev/null +++ b/vendor/symfony/console/Descriptor/DescriptorInterface.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\Console\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Descriptor interface. + * + * @author Jean-François Simon + */ +interface DescriptorInterface +{ + /** + * Describes an InputArgument instance. + * + * @param OutputInterface $output + * @param object $object + * @param array $options + */ + public function describe(OutputInterface $output, $object, array $options = array()); +} diff --git a/vendor/symfony/console/Descriptor/JsonDescriptor.php b/vendor/symfony/console/Descriptor/JsonDescriptor.php new file mode 100644 index 00000000..a5114c6d --- /dev/null +++ b/vendor/symfony/console/Descriptor/JsonDescriptor.php @@ -0,0 +1,179 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * JSON descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class JsonDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + $this->writeData($this->getInputArgumentData($argument), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + $this->writeData($this->getInputOptionData($option), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $this->writeData($this->getInputDefinitionData($definition), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $this->writeData($this->getCommandData($command), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace, true); + $commands = array(); + + foreach ($description->getCommands() as $command) { + $commands[] = $this->getCommandData($command); + } + + $data = array(); + if ('UNKNOWN' !== $application->getName()) { + $data['application']['name'] = $application->getName(); + if ('UNKNOWN' !== $application->getVersion()) { + $data['application']['version'] = $application->getVersion(); + } + } + + $data['commands'] = $commands; + + if ($describedNamespace) { + $data['namespace'] = $describedNamespace; + } else { + $data['namespaces'] = array_values($description->getNamespaces()); + } + + $this->writeData($data, $options); + } + + /** + * Writes data as json. + * + * @param array $data + * @param array $options + * + * @return array|string + */ + private function writeData(array $data, array $options) + { + $this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0)); + } + + /** + * @param InputArgument $argument + * + * @return array + */ + private function getInputArgumentData(InputArgument $argument) + { + return array( + 'name' => $argument->getName(), + 'is_required' => $argument->isRequired(), + 'is_array' => $argument->isArray(), + 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()), + 'default' => $argument->getDefault(), + ); + } + + /** + * @param InputOption $option + * + * @return array + */ + private function getInputOptionData(InputOption $option) + { + return array( + 'name' => '--'.$option->getName(), + 'shortcut' => $option->getShortcut() ? '-'.implode('|-', explode('|', $option->getShortcut())) : '', + 'accept_value' => $option->acceptValue(), + 'is_value_required' => $option->isValueRequired(), + 'is_multiple' => $option->isArray(), + 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), + 'default' => $option->getDefault(), + ); + } + + /** + * @param InputDefinition $definition + * + * @return array + */ + private function getInputDefinitionData(InputDefinition $definition) + { + $inputArguments = array(); + foreach ($definition->getArguments() as $name => $argument) { + $inputArguments[$name] = $this->getInputArgumentData($argument); + } + + $inputOptions = array(); + foreach ($definition->getOptions() as $name => $option) { + $inputOptions[$name] = $this->getInputOptionData($option); + } + + return array('arguments' => $inputArguments, 'options' => $inputOptions); + } + + /** + * @param Command $command + * + * @return array + */ + private function getCommandData(Command $command) + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + return array( + 'name' => $command->getName(), + 'usage' => array_merge(array($command->getSynopsis()), $command->getUsages(), $command->getAliases()), + 'description' => $command->getDescription(), + 'help' => $command->getProcessedHelp(), + 'definition' => $this->getInputDefinitionData($command->getNativeDefinition()), + 'hidden' => $command->isHidden(), + ); + } +} diff --git a/vendor/symfony/console/Descriptor/MarkdownDescriptor.php b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php new file mode 100644 index 00000000..106bff51 --- /dev/null +++ b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Markdown descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class MarkdownDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + public function describe(OutputInterface $output, $object, array $options = array()) + { + $decorated = $output->isDecorated(); + $output->setDecorated(false); + + parent::describe($output, $object, $options); + + $output->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + protected function write($content, $decorated = true) + { + parent::write($content, $decorated); + } + + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + $this->write( + '#### `'.($argument->getName() ?: '')."`\n\n" + .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '') + .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" + .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" + .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' + ); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + $name = '--'.$option->getName(); + if ($option->getShortcut()) { + $name .= '|-'.implode('|-', explode('|', $option->getShortcut())).''; + } + + $this->write( + '#### `'.$name.'`'."\n\n" + .($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '') + .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" + .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" + .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" + .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' + ); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + if ($showArguments = count($definition->getArguments()) > 0) { + $this->write('### Arguments'); + foreach ($definition->getArguments() as $argument) { + $this->write("\n\n"); + $this->write($this->describeInputArgument($argument)); + } + } + + if (count($definition->getOptions()) > 0) { + if ($showArguments) { + $this->write("\n\n"); + } + + $this->write('### Options'); + foreach ($definition->getOptions() as $option) { + $this->write("\n\n"); + $this->write($this->describeInputOption($option)); + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + $this->write( + '`'.$command->getName()."`\n" + .str_repeat('-', Helper::strlen($command->getName()) + 2)."\n\n" + .($command->getDescription() ? $command->getDescription()."\n\n" : '') + .'### Usage'."\n\n" + .array_reduce(array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()), function ($carry, $usage) { + return $carry.'* `'.$usage.'`'."\n"; + }) + ); + + if ($help = $command->getProcessedHelp()) { + $this->write("\n"); + $this->write($help); + } + + if ($command->getNativeDefinition()) { + $this->write("\n\n"); + $this->describeInputDefinition($command->getNativeDefinition()); + } + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace); + $title = $this->getApplicationTitle($application); + + $this->write($title."\n".str_repeat('=', Helper::strlen($title))); + + foreach ($description->getNamespaces() as $namespace) { + if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->write("\n\n"); + $this->write('**'.$namespace['id'].':**'); + } + + $this->write("\n\n"); + $this->write(implode("\n", array_map(function ($commandName) use ($description) { + return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())); + }, $namespace['commands']))); + } + + foreach ($description->getCommands() as $command) { + $this->write("\n\n"); + $this->write($this->describeCommand($command)); + } + } + + private function getApplicationTitle(Application $application) + { + if ('UNKNOWN' !== $application->getName()) { + if ('UNKNOWN' !== $application->getVersion()) { + return sprintf('%s %s', $application->getName(), $application->getVersion()); + } + + return $application->getName(); + } + + return 'Console Tool'; + } +} diff --git a/vendor/symfony/console/Descriptor/TextDescriptor.php b/vendor/symfony/console/Descriptor/TextDescriptor.php new file mode 100644 index 00000000..1829405c --- /dev/null +++ b/vendor/symfony/console/Descriptor/TextDescriptor.php @@ -0,0 +1,340 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * Text descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class TextDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : Helper::strlen($argument->getName()); + $spacingWidth = $totalWidth - strlen($argument->getName()); + + $this->writeText(sprintf(' %s %s%s%s', + $argument->getName(), + str_repeat(' ', $spacingWidth), + // + 4 = 2 spaces before , 2 spaces after + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()), + $default + ), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + if ($option->acceptValue() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $value = ''; + if ($option->acceptValue()) { + $value = '='.strtoupper($option->getName()); + + if ($option->isValueOptional()) { + $value = '['.$value.']'; + } + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions(array($option)); + $synopsis = sprintf('%s%s', + $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', + sprintf('--%s%s', $option->getName(), $value) + ); + + $spacingWidth = $totalWidth - Helper::strlen($synopsis); + + $this->writeText(sprintf(' %s %s%s%s%s', + $synopsis, + str_repeat(' ', $spacingWidth), + // + 4 = 2 spaces before , 2 spaces after + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()), + $default, + $option->isArray() ? ' (multiple values allowed)' : '' + ), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); + foreach ($definition->getArguments() as $argument) { + $totalWidth = max($totalWidth, Helper::strlen($argument->getName())); + } + + if ($definition->getArguments()) { + $this->writeText('Arguments:', $options); + $this->writeText("\n"); + foreach ($definition->getArguments() as $argument) { + $this->describeInputArgument($argument, array_merge($options, array('total_width' => $totalWidth))); + $this->writeText("\n"); + } + } + + if ($definition->getArguments() && $definition->getOptions()) { + $this->writeText("\n"); + } + + if ($definition->getOptions()) { + $laterOptions = array(); + + $this->writeText('Options:', $options); + foreach ($definition->getOptions() as $option) { + if (strlen($option->getShortcut()) > 1) { + $laterOptions[] = $option; + continue; + } + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth))); + } + foreach ($laterOptions as $option) { + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth))); + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $command->getSynopsis(true); + $command->getSynopsis(false); + $command->mergeApplicationDefinition(false); + + $this->writeText('Usage:', $options); + foreach (array_merge(array($command->getSynopsis(true)), $command->getAliases(), $command->getUsages()) as $usage) { + $this->writeText("\n"); + $this->writeText(' '.$usage, $options); + } + $this->writeText("\n"); + + $definition = $command->getNativeDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->writeText("\n"); + $this->describeInputDefinition($definition, $options); + $this->writeText("\n"); + } + + if ($help = $command->getProcessedHelp()) { + $this->writeText("\n"); + $this->writeText('Help:', $options); + $this->writeText("\n"); + $this->writeText(' '.str_replace("\n", "\n ", $help), $options); + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace); + + if (isset($options['raw_text']) && $options['raw_text']) { + $width = $this->getColumnWidth($description->getCommands()); + + foreach ($description->getCommands() as $command) { + $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options); + $this->writeText("\n"); + } + } else { + if ('' != $help = $application->getHelp()) { + $this->writeText("$help\n\n", $options); + } + + $this->writeText("Usage:\n", $options); + $this->writeText(" command [options] [arguments]\n\n", $options); + + $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options); + + $this->writeText("\n"); + $this->writeText("\n"); + + $commands = $description->getCommands(); + $namespaces = $description->getNamespaces(); + if ($describedNamespace && $namespaces) { + // make sure all alias commands are included when describing a specific namespace + $describedNamespaceInfo = reset($namespaces); + foreach ($describedNamespaceInfo['commands'] as $name) { + $commands[$name] = $description->getCommand($name); + } + } + + // calculate max. width based on available commands per namespace + $width = $this->getColumnWidth(call_user_func_array('array_merge', array_map(function ($namespace) use ($commands) { + return array_intersect($namespace['commands'], array_keys($commands)); + }, $namespaces))); + + if ($describedNamespace) { + $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); + } else { + $this->writeText('Available commands:', $options); + } + + foreach ($namespaces as $namespace) { + $namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) { + return isset($commands[$name]); + }); + + if (!$namespace['commands']) { + continue; + } + + if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->writeText("\n"); + $this->writeText(' '.$namespace['id'].'', $options); + } + + foreach ($namespace['commands'] as $name) { + $this->writeText("\n"); + $spacingWidth = $width - Helper::strlen($name); + $command = $commands[$name]; + $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : ''; + $this->writeText(sprintf(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options); + } + } + + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + private function writeText($content, array $options = array()) + { + $this->write( + isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, + isset($options['raw_output']) ? !$options['raw_output'] : true + ); + } + + /** + * Formats command aliases to show them in the command description. + * + * @param Command $command + * + * @return string + */ + private function getCommandAliasesText($command) + { + $text = ''; + $aliases = $command->getAliases(); + + if ($aliases) { + $text = '['.implode('|', $aliases).'] '; + } + + return $text; + } + + /** + * Formats input option/argument default value. + * + * @param mixed $default + * + * @return string + */ + private function formatDefaultValue($default) + { + if (is_string($default)) { + $default = OutputFormatter::escape($default); + } elseif (is_array($default)) { + foreach ($default as $key => $value) { + if (is_string($value)) { + $default[$key] = OutputFormatter::escape($value); + } + } + } + + return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + } + + /** + * @param (Command|string)[] $commands + * + * @return int + */ + private function getColumnWidth(array $commands) + { + $widths = array(); + + foreach ($commands as $command) { + if ($command instanceof Command) { + $widths[] = Helper::strlen($command->getName()); + foreach ($command->getAliases() as $alias) { + $widths[] = Helper::strlen($alias); + } + } else { + $widths[] = Helper::strlen($command); + } + } + + return $widths ? max($widths) + 2 : 0; + } + + /** + * @param InputOption[] $options + * + * @return int + */ + private function calculateTotalWidthForOptions($options) + { + $totalWidth = 0; + foreach ($options as $option) { + // "-" + shortcut + ", --" + name + $nameLength = 1 + max(Helper::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName()); + + if ($option->acceptValue()) { + $valueLength = 1 + Helper::strlen($option->getName()); // = + value + $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] + + $nameLength += $valueLength; + } + $totalWidth = max($totalWidth, $nameLength); + } + + return $totalWidth; + } +} diff --git a/vendor/symfony/console/Descriptor/XmlDescriptor.php b/vendor/symfony/console/Descriptor/XmlDescriptor.php new file mode 100644 index 00000000..03a26cc8 --- /dev/null +++ b/vendor/symfony/console/Descriptor/XmlDescriptor.php @@ -0,0 +1,264 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * XML descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class XmlDescriptor extends Descriptor +{ + /** + * @param InputDefinition $definition + * + * @return \DOMDocument + */ + public function getInputDefinitionDocument(InputDefinition $definition) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($definitionXML = $dom->createElement('definition')); + + $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); + foreach ($definition->getArguments() as $argument) { + $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument)); + } + + $definitionXML->appendChild($optionsXML = $dom->createElement('options')); + foreach ($definition->getOptions() as $option) { + $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); + } + + return $dom; + } + + /** + * @param Command $command + * + * @return \DOMDocument + */ + public function getCommandDocument(Command $command) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($commandXML = $dom->createElement('command')); + + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + $commandXML->setAttribute('id', $command->getName()); + $commandXML->setAttribute('name', $command->getName()); + $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0); + + $commandXML->appendChild($usagesXML = $dom->createElement('usages')); + + foreach (array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()) as $usage) { + $usagesXML->appendChild($dom->createElement('usage', $usage)); + } + + $commandXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); + + $commandXML->appendChild($helpXML = $dom->createElement('help')); + $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); + + $definitionXML = $this->getInputDefinitionDocument($command->getNativeDefinition()); + $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); + + return $dom; + } + + /** + * @param Application $application + * @param string|null $namespace + * + * @return \DOMDocument + */ + public function getApplicationDocument(Application $application, $namespace = null) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($rootXml = $dom->createElement('symfony')); + + if ($application->getName() !== 'UNKNOWN') { + $rootXml->setAttribute('name', $application->getName()); + if ($application->getVersion() !== 'UNKNOWN') { + $rootXml->setAttribute('version', $application->getVersion()); + } + } + + $rootXml->appendChild($commandsXML = $dom->createElement('commands')); + + $description = new ApplicationDescription($application, $namespace, true); + + if ($namespace) { + $commandsXML->setAttribute('namespace', $namespace); + } + + foreach ($description->getCommands() as $command) { + $this->appendDocument($commandsXML, $this->getCommandDocument($command)); + } + + if (!$namespace) { + $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); + + foreach ($description->getNamespaces() as $namespaceDescription) { + $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); + $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); + + foreach ($namespaceDescription['commands'] as $name) { + $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); + $commandXML->appendChild($dom->createTextNode($name)); + } + } + } + + return $dom; + } + + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + $this->writeDocument($this->getInputArgumentDocument($argument)); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + $this->writeDocument($this->getInputOptionDocument($option)); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $this->writeDocument($this->getInputDefinitionDocument($definition)); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $this->writeDocument($this->getCommandDocument($command)); + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null)); + } + + /** + * Appends document children to parent node. + * + * @param \DOMNode $parentNode + * @param \DOMNode $importedParent + */ + private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) + { + foreach ($importedParent->childNodes as $childNode) { + $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); + } + } + + /** + * Writes DOM document. + * + * @param \DOMDocument $dom + * + * @return \DOMDocument|string + */ + private function writeDocument(\DOMDocument $dom) + { + $dom->formatOutput = true; + $this->write($dom->saveXML()); + } + + /** + * @param InputArgument $argument + * + * @return \DOMDocument + */ + private function getInputArgumentDocument(InputArgument $argument) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('argument')); + $objectXML->setAttribute('name', $argument->getName()); + $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); + $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); + + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + $defaults = is_array($argument->getDefault()) ? $argument->getDefault() : (is_bool($argument->getDefault()) ? array(var_export($argument->getDefault(), true)) : ($argument->getDefault() ? array($argument->getDefault()) : array())); + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + + return $dom; + } + + /** + * @param InputOption $option + * + * @return \DOMDocument + */ + private function getInputOptionDocument(InputOption $option) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('option')); + $objectXML->setAttribute('name', '--'.$option->getName()); + $pos = strpos($option->getShortcut(), '|'); + if (false !== $pos) { + $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos)); + $objectXML->setAttribute('shortcuts', '-'.implode('|-', explode('|', $option->getShortcut()))); + } else { + $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); + } + $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); + $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); + $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); + + if ($option->acceptValue()) { + $defaults = is_array($option->getDefault()) ? $option->getDefault() : (is_bool($option->getDefault()) ? array(var_export($option->getDefault(), true)) : ($option->getDefault() ? array($option->getDefault()) : array())); + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + + if (!empty($defaults)) { + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + } + } + + return $dom; + } +} diff --git a/vendor/symfony/console/Event/ConsoleCommandEvent.php b/vendor/symfony/console/Event/ConsoleCommandEvent.php new file mode 100644 index 00000000..92adf1ef --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleCommandEvent.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\Console\Event; + +/** + * Allows to do things before the command is executed, like skipping the command or changing the input. + * + * @author Fabien Potencier + */ +class ConsoleCommandEvent extends ConsoleEvent +{ + /** + * The return code for skipped commands, this will also be passed into the terminate event. + */ + const RETURN_CODE_DISABLED = 113; + + /** + * Indicates if the command should be run or skipped. + * + * @var bool + */ + private $commandShouldRun = true; + + /** + * Disables the command, so it won't be run. + * + * @return bool + */ + public function disableCommand() + { + return $this->commandShouldRun = false; + } + + /** + * Enables the command. + * + * @return bool + */ + public function enableCommand() + { + return $this->commandShouldRun = true; + } + + /** + * Returns true if the command is runnable, false otherwise. + * + * @return bool + */ + public function commandShouldRun() + { + return $this->commandShouldRun; + } +} diff --git a/vendor/symfony/console/Event/ConsoleErrorEvent.php b/vendor/symfony/console/Event/ConsoleErrorEvent.php new file mode 100644 index 00000000..49edb723 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleErrorEvent.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\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to handle throwables thrown while running a command. + * + * @author Wouter de Jong + */ +final class ConsoleErrorEvent extends ConsoleEvent +{ + private $error; + private $exitCode; + + public function __construct(InputInterface $input, OutputInterface $output, $error, Command $command = null) + { + parent::__construct($command, $input, $output); + + $this->setError($error); + } + + /** + * Returns the thrown error/exception. + * + * @return \Throwable + */ + public function getError() + { + return $this->error; + } + + /** + * Replaces the thrown error/exception. + * + * @param \Throwable $error + */ + public function setError($error) + { + if (!$error instanceof \Throwable && !$error instanceof \Exception) { + throw new InvalidArgumentException(sprintf('The error passed to ConsoleErrorEvent must be an instance of \Throwable or \Exception, "%s" was passed instead.', is_object($error) ? get_class($error) : gettype($error))); + } + + $this->error = $error; + } + + /** + * Sets the exit code. + * + * @param int $exitCode The command exit code + */ + public function setExitCode($exitCode) + { + $this->exitCode = (int) $exitCode; + + $r = new \ReflectionProperty($this->error, 'code'); + $r->setAccessible(true); + $r->setValue($this->error, $this->exitCode); + } + + /** + * Gets the exit code. + * + * @return int The command exit code + */ + public function getExitCode() + { + return null !== $this->exitCode ? $this->exitCode : ($this->error->getCode() ?: 1); + } +} diff --git a/vendor/symfony/console/Event/ConsoleEvent.php b/vendor/symfony/console/Event/ConsoleEvent.php new file mode 100644 index 00000000..5440da21 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleEvent.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\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\EventDispatcher\Event; + +/** + * Allows to inspect input and output of a command. + * + * @author Francesco Levorato + */ +class ConsoleEvent extends Event +{ + protected $command; + + private $input; + private $output; + + public function __construct(Command $command = null, InputInterface $input, OutputInterface $output) + { + $this->command = $command; + $this->input = $input; + $this->output = $output; + } + + /** + * Gets the command that is executed. + * + * @return Command|null A Command instance + */ + public function getCommand() + { + return $this->command; + } + + /** + * Gets the input instance. + * + * @return InputInterface An InputInterface instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance. + * + * @return OutputInterface An OutputInterface instance + */ + public function getOutput() + { + return $this->output; + } +} diff --git a/vendor/symfony/console/Event/ConsoleExceptionEvent.php b/vendor/symfony/console/Event/ConsoleExceptionEvent.php new file mode 100644 index 00000000..a31797fa --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleExceptionEvent.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\Console\Event; + +@trigger_error(sprintf('The "%s" class is deprecated since version 3.3 and will be removed in 4.0. Use the ConsoleErrorEvent instead.', ConsoleExceptionEvent::class), E_USER_DEPRECATED); + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to handle exception thrown in a command. + * + * @author Fabien Potencier + * + * @deprecated since version 3.3, to be removed in 4.0. Use ConsoleErrorEvent instead. + */ +class ConsoleExceptionEvent extends ConsoleEvent +{ + private $exception; + private $exitCode; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode) + { + parent::__construct($command, $input, $output); + + $this->setException($exception); + $this->exitCode = (int) $exitCode; + } + + /** + * Returns the thrown exception. + * + * @return \Exception The thrown exception + */ + public function getException() + { + return $this->exception; + } + + /** + * Replaces the thrown exception. + * + * This exception will be thrown if no response is set in the event. + * + * @param \Exception $exception The thrown exception + */ + public function setException(\Exception $exception) + { + $this->exception = $exception; + } + + /** + * Gets the exit code. + * + * @return int The command exit code + */ + public function getExitCode() + { + return $this->exitCode; + } +} diff --git a/vendor/symfony/console/Event/ConsoleTerminateEvent.php b/vendor/symfony/console/Event/ConsoleTerminateEvent.php new file mode 100644 index 00000000..b6a5d7c0 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleTerminateEvent.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\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to manipulate the exit code of a command after its execution. + * + * @author Francesco Levorato + */ +class ConsoleTerminateEvent extends ConsoleEvent +{ + /** + * The exit code of the command. + * + * @var int + */ + private $exitCode; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output, $exitCode) + { + parent::__construct($command, $input, $output); + + $this->setExitCode($exitCode); + } + + /** + * Sets the exit code. + * + * @param int $exitCode The command exit code + */ + public function setExitCode($exitCode) + { + $this->exitCode = (int) $exitCode; + } + + /** + * Gets the exit code. + * + * @return int The command exit code + */ + public function getExitCode() + { + return $this->exitCode; + } +} diff --git a/vendor/symfony/console/EventListener/ErrorListener.php b/vendor/symfony/console/EventListener/ErrorListener.php new file mode 100644 index 00000000..8e35d97d --- /dev/null +++ b/vendor/symfony/console/EventListener/ErrorListener.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * @author James Halsall + * @author Robin Chalas + */ +class ErrorListener implements EventSubscriberInterface +{ + private $logger; + + public function __construct(LoggerInterface $logger = null) + { + $this->logger = $logger; + } + + public function onConsoleError(ConsoleErrorEvent $event) + { + if (null === $this->logger) { + return; + } + + $error = $event->getError(); + + if (!$inputString = $this->getInputString($event)) { + return $this->logger->error('An error occurred while using the console. Message: "{message}"', array('error' => $error, 'message' => $error->getMessage())); + } + + $this->logger->error('Error thrown while running command "{command}". Message: "{message}"', array('error' => $error, 'command' => $inputString, 'message' => $error->getMessage())); + } + + public function onConsoleTerminate(ConsoleTerminateEvent $event) + { + if (null === $this->logger) { + return; + } + + $exitCode = $event->getExitCode(); + + if (0 === $exitCode) { + return; + } + + if (!$inputString = $this->getInputString($event)) { + return $this->logger->error('The console exited with code "{code}"', array('code' => $exitCode)); + } + + $this->logger->error('Command "{command}" exited with code "{code}"', array('command' => $inputString, 'code' => $exitCode)); + } + + public static function getSubscribedEvents() + { + return array( + ConsoleEvents::ERROR => array('onConsoleError', -128), + ConsoleEvents::TERMINATE => array('onConsoleTerminate', -128), + ); + } + + private static function getInputString(ConsoleEvent $event) + { + $commandName = $event->getCommand() ? $event->getCommand()->getName() : null; + $input = $event->getInput(); + + if (method_exists($input, '__toString')) { + if ($commandName) { + return str_replace(array("'$commandName'", "\"$commandName\""), $commandName, (string) $input); + } + + return (string) $input; + } + + return $commandName; + } +} diff --git a/vendor/symfony/console/Exception/CommandNotFoundException.php b/vendor/symfony/console/Exception/CommandNotFoundException.php new file mode 100644 index 00000000..54f1a5b0 --- /dev/null +++ b/vendor/symfony/console/Exception/CommandNotFoundException.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\Console\Exception; + +/** + * Represents an incorrect command name typed in the console. + * + * @author Jérôme Tamarelle + */ +class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface +{ + private $alternatives; + + /** + * @param string $message Exception message to throw + * @param array $alternatives List of similar defined names + * @param int $code Exception code + * @param Exception $previous previous exception used for the exception chaining + */ + public function __construct($message, array $alternatives = array(), $code = 0, \Exception $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->alternatives = $alternatives; + } + + /** + * @return array A list of similar defined names + */ + public function getAlternatives() + { + return $this->alternatives; + } +} diff --git a/vendor/symfony/console/Exception/ExceptionInterface.php b/vendor/symfony/console/Exception/ExceptionInterface.php new file mode 100644 index 00000000..491cc4c6 --- /dev/null +++ b/vendor/symfony/console/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\Console\Exception; + +/** + * ExceptionInterface. + * + * @author Jérôme Tamarelle + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/InvalidArgumentException.php b/vendor/symfony/console/Exception/InvalidArgumentException.php new file mode 100644 index 00000000..07cc0b61 --- /dev/null +++ b/vendor/symfony/console/Exception/InvalidArgumentException.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\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/InvalidOptionException.php b/vendor/symfony/console/Exception/InvalidOptionException.php new file mode 100644 index 00000000..b2eec616 --- /dev/null +++ b/vendor/symfony/console/Exception/InvalidOptionException.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\Console\Exception; + +/** + * Represents an incorrect option name typed in the console. + * + * @author Jérôme Tamarelle + */ +class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/LogicException.php b/vendor/symfony/console/Exception/LogicException.php new file mode 100644 index 00000000..fc37b8d8 --- /dev/null +++ b/vendor/symfony/console/Exception/LogicException.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\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/RuntimeException.php b/vendor/symfony/console/Exception/RuntimeException.php new file mode 100644 index 00000000..51d7d80a --- /dev/null +++ b/vendor/symfony/console/Exception/RuntimeException.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\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Formatter/OutputFormatter.php b/vendor/symfony/console/Formatter/OutputFormatter.php new file mode 100644 index 00000000..d32f0915 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatter.php @@ -0,0 +1,241 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * Formatter class for console output. + * + * @author Konstantin Kudryashov + */ +class OutputFormatter implements OutputFormatterInterface +{ + private $decorated; + private $styles = array(); + private $styleStack; + + /** + * Escapes "<" special char in given text. + * + * @param string $text Text to escape + * + * @return string Escaped text + */ + public static function escape($text) + { + $text = preg_replace('/([^\\\\]?) FormatterStyle" instances + */ + public function __construct($decorated = false, array $styles = array()) + { + $this->decorated = (bool) $decorated; + + $this->setStyle('error', new OutputFormatterStyle('white', 'red')); + $this->setStyle('info', new OutputFormatterStyle('green')); + $this->setStyle('comment', new OutputFormatterStyle('yellow')); + $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); + + foreach ($styles as $name => $style) { + $this->setStyle($name, $style); + } + + $this->styleStack = new OutputFormatterStyleStack(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + $this->decorated = (bool) $decorated; + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->decorated; + } + + /** + * {@inheritdoc} + */ + public function setStyle($name, OutputFormatterStyleInterface $style) + { + $this->styles[strtolower($name)] = $style; + } + + /** + * {@inheritdoc} + */ + public function hasStyle($name) + { + return isset($this->styles[strtolower($name)]); + } + + /** + * {@inheritdoc} + */ + public function getStyle($name) + { + if (!$this->hasStyle($name)) { + throw new InvalidArgumentException(sprintf('Undefined style: %s', $name)); + } + + return $this->styles[strtolower($name)]; + } + + /** + * {@inheritdoc} + */ + public function format($message) + { + $message = (string) $message; + $offset = 0; + $output = ''; + $tagRegex = '[a-z][a-z0-9,_=;-]*+'; + preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $i => $match) { + $pos = $match[1]; + $text = $match[0]; + + if (0 != $pos && '\\' == $message[$pos - 1]) { + continue; + } + + // add the text up to the next tag + $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); + $offset = $pos + strlen($text); + + // opening tag? + if ($open = '/' != $text[1]) { + $tag = $matches[1][$i][0]; + } else { + $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : ''; + } + + if (!$open && !$tag) { + // + $this->styleStack->pop(); + } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) { + $output .= $this->applyCurrentStyle($text); + } elseif ($open) { + $this->styleStack->push($style); + } else { + $this->styleStack->pop($style); + } + } + + $output .= $this->applyCurrentStyle(substr($message, $offset)); + + if (false !== strpos($output, '<<')) { + return strtr($output, array('\\<' => '<', '<<' => '\\')); + } + + return str_replace('\\<', '<', $output); + } + + /** + * @return OutputFormatterStyleStack + */ + public function getStyleStack() + { + return $this->styleStack; + } + + /** + * Tries to create new style instance from string. + * + * @param string $string + * + * @return OutputFormatterStyle|false false if string is not format string + */ + private function createStyleFromString($string) + { + if (isset($this->styles[$string])) { + return $this->styles[$string]; + } + + if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, PREG_SET_ORDER)) { + return false; + } + + $style = new OutputFormatterStyle(); + foreach ($matches as $match) { + array_shift($match); + + if ('fg' == $match[0]) { + $style->setForeground($match[1]); + } elseif ('bg' == $match[0]) { + $style->setBackground($match[1]); + } elseif ('options' === $match[0]) { + preg_match_all('([^,;]+)', $match[1], $options); + $options = array_shift($options); + foreach ($options as $option) { + try { + $style->setOption($option); + } catch (\InvalidArgumentException $e) { + @trigger_error(sprintf('Unknown style options are deprecated since version 3.2 and will be removed in 4.0. Exception "%s".', $e->getMessage()), E_USER_DEPRECATED); + + return false; + } + } + } else { + return false; + } + } + + return $style; + } + + /** + * Applies current style from stack to text, if must be applied. + * + * @param string $text Input text + * + * @return string Styled text + */ + private function applyCurrentStyle($text) + { + return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterInterface.php b/vendor/symfony/console/Formatter/OutputFormatterInterface.php new file mode 100644 index 00000000..281e240c --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterInterface.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\Console\Formatter; + +/** + * Formatter interface for console output. + * + * @author Konstantin Kudryashov + */ +interface OutputFormatterInterface +{ + /** + * Sets the decorated flag. + * + * @param bool $decorated Whether to decorate the messages or not + */ + public function setDecorated($decorated); + + /** + * Gets the decorated flag. + * + * @return bool true if the output will decorate messages, false otherwise + */ + public function isDecorated(); + + /** + * Sets a new style. + * + * @param string $name The style name + * @param OutputFormatterStyleInterface $style The style instance + */ + public function setStyle($name, OutputFormatterStyleInterface $style); + + /** + * Checks if output formatter has style with specified name. + * + * @param string $name + * + * @return bool + */ + public function hasStyle($name); + + /** + * Gets style options from style with specified name. + * + * @param string $name + * + * @return OutputFormatterStyleInterface + * + * @throws \InvalidArgumentException When style isn't defined + */ + public function getStyle($name); + + /** + * Formats a message according to the given styles. + * + * @param string $message The message to style + * + * @return string The styled message + */ + public function format($message); +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyle.php b/vendor/symfony/console/Formatter/OutputFormatterStyle.php new file mode 100644 index 00000000..c7c6b4a0 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyle.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * Formatter style class for defining styles. + * + * @author Konstantin Kudryashov + */ +class OutputFormatterStyle implements OutputFormatterStyleInterface +{ + private static $availableForegroundColors = array( + 'black' => array('set' => 30, 'unset' => 39), + 'red' => array('set' => 31, 'unset' => 39), + 'green' => array('set' => 32, 'unset' => 39), + 'yellow' => array('set' => 33, 'unset' => 39), + 'blue' => array('set' => 34, 'unset' => 39), + 'magenta' => array('set' => 35, 'unset' => 39), + 'cyan' => array('set' => 36, 'unset' => 39), + 'white' => array('set' => 37, 'unset' => 39), + 'default' => array('set' => 39, 'unset' => 39), + ); + private static $availableBackgroundColors = array( + 'black' => array('set' => 40, 'unset' => 49), + 'red' => array('set' => 41, 'unset' => 49), + 'green' => array('set' => 42, 'unset' => 49), + 'yellow' => array('set' => 43, 'unset' => 49), + 'blue' => array('set' => 44, 'unset' => 49), + 'magenta' => array('set' => 45, 'unset' => 49), + 'cyan' => array('set' => 46, 'unset' => 49), + 'white' => array('set' => 47, 'unset' => 49), + 'default' => array('set' => 49, 'unset' => 49), + ); + private static $availableOptions = array( + 'bold' => array('set' => 1, 'unset' => 22), + 'underscore' => array('set' => 4, 'unset' => 24), + 'blink' => array('set' => 5, 'unset' => 25), + 'reverse' => array('set' => 7, 'unset' => 27), + 'conceal' => array('set' => 8, 'unset' => 28), + ); + + private $foreground; + private $background; + private $options = array(); + + /** + * Initializes output formatter style. + * + * @param string|null $foreground The style foreground color name + * @param string|null $background The style background color name + * @param array $options The style options + */ + public function __construct($foreground = null, $background = null, array $options = array()) + { + if (null !== $foreground) { + $this->setForeground($foreground); + } + if (null !== $background) { + $this->setBackground($background); + } + if (count($options)) { + $this->setOptions($options); + } + } + + /** + * Sets style foreground color. + * + * @param string|null $color The color name + * + * @throws InvalidArgumentException When the color name isn't defined + */ + public function setForeground($color = null) + { + if (null === $color) { + $this->foreground = null; + + return; + } + + if (!isset(static::$availableForegroundColors[$color])) { + throw new InvalidArgumentException(sprintf( + 'Invalid foreground color specified: "%s". Expected one of (%s)', + $color, + implode(', ', array_keys(static::$availableForegroundColors)) + )); + } + + $this->foreground = static::$availableForegroundColors[$color]; + } + + /** + * Sets style background color. + * + * @param string|null $color The color name + * + * @throws InvalidArgumentException When the color name isn't defined + */ + public function setBackground($color = null) + { + if (null === $color) { + $this->background = null; + + return; + } + + if (!isset(static::$availableBackgroundColors[$color])) { + throw new InvalidArgumentException(sprintf( + 'Invalid background color specified: "%s". Expected one of (%s)', + $color, + implode(', ', array_keys(static::$availableBackgroundColors)) + )); + } + + $this->background = static::$availableBackgroundColors[$color]; + } + + /** + * Sets some specific style option. + * + * @param string $option The option name + * + * @throws InvalidArgumentException When the option name isn't defined + */ + public function setOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new InvalidArgumentException(sprintf( + 'Invalid option specified: "%s". Expected one of (%s)', + $option, + implode(', ', array_keys(static::$availableOptions)) + )); + } + + if (!in_array(static::$availableOptions[$option], $this->options)) { + $this->options[] = static::$availableOptions[$option]; + } + } + + /** + * Unsets some specific style option. + * + * @param string $option The option name + * + * @throws InvalidArgumentException When the option name isn't defined + */ + public function unsetOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new InvalidArgumentException(sprintf( + 'Invalid option specified: "%s". Expected one of (%s)', + $option, + implode(', ', array_keys(static::$availableOptions)) + )); + } + + $pos = array_search(static::$availableOptions[$option], $this->options); + if (false !== $pos) { + unset($this->options[$pos]); + } + } + + /** + * Sets multiple style options at once. + * + * @param array $options + */ + public function setOptions(array $options) + { + $this->options = array(); + + foreach ($options as $option) { + $this->setOption($option); + } + } + + /** + * Applies the style to a given text. + * + * @param string $text The text to style + * + * @return string + */ + public function apply($text) + { + $setCodes = array(); + $unsetCodes = array(); + + if (null !== $this->foreground) { + $setCodes[] = $this->foreground['set']; + $unsetCodes[] = $this->foreground['unset']; + } + if (null !== $this->background) { + $setCodes[] = $this->background['set']; + $unsetCodes[] = $this->background['unset']; + } + if (count($this->options)) { + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + $unsetCodes[] = $option['unset']; + } + } + + if (0 === count($setCodes)) { + return $text; + } + + return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php b/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php new file mode 100644 index 00000000..c36fda80 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.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\Console\Formatter; + +/** + * Formatter style interface for defining styles. + * + * @author Konstantin Kudryashov + */ +interface OutputFormatterStyleInterface +{ + /** + * Sets style foreground color. + * + * @param string $color The color name + */ + public function setForeground($color = null); + + /** + * Sets style background color. + * + * @param string $color The color name + */ + public function setBackground($color = null); + + /** + * Sets some specific style option. + * + * @param string $option The option name + */ + public function setOption($option); + + /** + * Unsets some specific style option. + * + * @param string $option The option name + */ + public function unsetOption($option); + + /** + * Sets multiple style options at once. + * + * @param array $options + */ + public function setOptions(array $options); + + /** + * Applies the style to a given text. + * + * @param string $text The text to style + * + * @return string + */ + public function apply($text); +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php b/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php new file mode 100644 index 00000000..0a531d29 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Jean-François Simon + */ +class OutputFormatterStyleStack +{ + /** + * @var OutputFormatterStyleInterface[] + */ + private $styles; + + /** + * @var OutputFormatterStyleInterface + */ + private $emptyStyle; + + /** + * Constructor. + * + * @param OutputFormatterStyleInterface|null $emptyStyle + */ + public function __construct(OutputFormatterStyleInterface $emptyStyle = null) + { + $this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle(); + $this->reset(); + } + + /** + * Resets stack (ie. empty internal arrays). + */ + public function reset() + { + $this->styles = array(); + } + + /** + * Pushes a style in the stack. + * + * @param OutputFormatterStyleInterface $style + */ + public function push(OutputFormatterStyleInterface $style) + { + $this->styles[] = $style; + } + + /** + * Pops a style from the stack. + * + * @param OutputFormatterStyleInterface|null $style + * + * @return OutputFormatterStyleInterface + * + * @throws InvalidArgumentException When style tags incorrectly nested + */ + public function pop(OutputFormatterStyleInterface $style = null) + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + if (null === $style) { + return array_pop($this->styles); + } + + foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { + if ($style->apply('') === $stackedStyle->apply('')) { + $this->styles = array_slice($this->styles, 0, $index); + + return $stackedStyle; + } + } + + throw new InvalidArgumentException('Incorrectly nested style tag found.'); + } + + /** + * Computes current style with stacks top codes. + * + * @return OutputFormatterStyle + */ + public function getCurrent() + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + return $this->styles[count($this->styles) - 1]; + } + + /** + * @param OutputFormatterStyleInterface $emptyStyle + * + * @return $this + */ + public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + /** + * @return OutputFormatterStyleInterface + */ + public function getEmptyStyle() + { + return $this->emptyStyle; + } +} diff --git a/vendor/symfony/console/Helper/DebugFormatterHelper.php b/vendor/symfony/console/Helper/DebugFormatterHelper.php new file mode 100644 index 00000000..1119b795 --- /dev/null +++ b/vendor/symfony/console/Helper/DebugFormatterHelper.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Helps outputting debug information when running an external program from a command. + * + * An external program can be a Process, an HTTP request, or anything else. + * + * @author Fabien Potencier + */ +class DebugFormatterHelper extends Helper +{ + private $colors = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'); + private $started = array(); + private $count = -1; + + /** + * Starts a debug formatting session. + * + * @param string $id The id of the formatting session + * @param string $message The message to display + * @param string $prefix The prefix to use + * + * @return string + */ + public function start($id, $message, $prefix = 'RUN') + { + $this->started[$id] = array('border' => ++$this->count % count($this->colors)); + + return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); + } + + /** + * Adds progress to a formatting session. + * + * @param string $id The id of the formatting session + * @param string $buffer The message to display + * @param bool $error Whether to consider the buffer as error + * @param string $prefix The prefix for output + * @param string $errorPrefix The prefix for error output + * + * @return string + */ + public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR') + { + $message = ''; + + if ($error) { + if (isset($this->started[$id]['out'])) { + $message .= "\n"; + unset($this->started[$id]['out']); + } + if (!isset($this->started[$id]['err'])) { + $message .= sprintf('%s %s ', $this->getBorder($id), $errorPrefix); + $this->started[$id]['err'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $errorPrefix), $buffer); + } else { + if (isset($this->started[$id]['err'])) { + $message .= "\n"; + unset($this->started[$id]['err']); + } + if (!isset($this->started[$id]['out'])) { + $message .= sprintf('%s %s ', $this->getBorder($id), $prefix); + $this->started[$id]['out'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $prefix), $buffer); + } + + return $message; + } + + /** + * Stops a formatting session. + * + * @param string $id The id of the formatting session + * @param string $message The message to display + * @param bool $successful Whether to consider the result as success + * @param string $prefix The prefix for the end output + * + * @return string + */ + public function stop($id, $message, $successful, $prefix = 'RES') + { + $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; + + if ($successful) { + return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + } + + $message = sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + + unset($this->started[$id]['out'], $this->started[$id]['err']); + + return $message; + } + + /** + * @param string $id The id of the formatting session + * + * @return string + */ + private function getBorder($id) + { + return sprintf(' ', $this->colors[$this->started[$id]['border']]); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'debug_formatter'; + } +} diff --git a/vendor/symfony/console/Helper/DescriptorHelper.php b/vendor/symfony/console/Helper/DescriptorHelper.php new file mode 100644 index 00000000..300e6455 --- /dev/null +++ b/vendor/symfony/console/Helper/DescriptorHelper.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\Console\Helper; + +use Symfony\Component\Console\Descriptor\DescriptorInterface; +use Symfony\Component\Console\Descriptor\JsonDescriptor; +use Symfony\Component\Console\Descriptor\MarkdownDescriptor; +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * This class adds helper method to describe objects in various formats. + * + * @author Jean-François Simon + */ +class DescriptorHelper extends Helper +{ + /** + * @var DescriptorInterface[] + */ + private $descriptors = array(); + + /** + * Constructor. + */ + public function __construct() + { + $this + ->register('txt', new TextDescriptor()) + ->register('xml', new XmlDescriptor()) + ->register('json', new JsonDescriptor()) + ->register('md', new MarkdownDescriptor()) + ; + } + + /** + * Describes an object if supported. + * + * Available options are: + * * format: string, the output format name + * * raw_text: boolean, sets output type as raw + * + * @param OutputInterface $output + * @param object $object + * @param array $options + * + * @throws InvalidArgumentException when the given format is not supported + */ + public function describe(OutputInterface $output, $object, array $options = array()) + { + $options = array_merge(array( + 'raw_text' => false, + 'format' => 'txt', + ), $options); + + if (!isset($this->descriptors[$options['format']])) { + throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format'])); + } + + $descriptor = $this->descriptors[$options['format']]; + $descriptor->describe($output, $object, $options); + } + + /** + * Registers a descriptor. + * + * @param string $format + * @param DescriptorInterface $descriptor + * + * @return $this + */ + public function register($format, DescriptorInterface $descriptor) + { + $this->descriptors[$format] = $descriptor; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'descriptor'; + } +} diff --git a/vendor/symfony/console/Helper/FormatterHelper.php b/vendor/symfony/console/Helper/FormatterHelper.php new file mode 100644 index 00000000..6a48a77f --- /dev/null +++ b/vendor/symfony/console/Helper/FormatterHelper.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * The Formatter class provides helpers to format messages. + * + * @author Fabien Potencier + */ +class FormatterHelper extends Helper +{ + /** + * Formats a message within a section. + * + * @param string $section The section name + * @param string $message The message + * @param string $style The style to apply to the section + * + * @return string The format section + */ + public function formatSection($section, $message, $style = 'info') + { + return sprintf('<%s>[%s] %s', $style, $section, $style, $message); + } + + /** + * Formats a message as a block of text. + * + * @param string|array $messages The message to write in the block + * @param string $style The style to apply to the whole block + * @param bool $large Whether to return a large block + * + * @return string The formatter message + */ + public function formatBlock($messages, $style, $large = false) + { + if (!is_array($messages)) { + $messages = array($messages); + } + + $len = 0; + $lines = array(); + foreach ($messages as $message) { + $message = OutputFormatter::escape($message); + $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); + $len = max($this->strlen($message) + ($large ? 4 : 2), $len); + } + + $messages = $large ? array(str_repeat(' ', $len)) : array(); + for ($i = 0; isset($lines[$i]); ++$i) { + $messages[] = $lines[$i].str_repeat(' ', $len - $this->strlen($lines[$i])); + } + if ($large) { + $messages[] = str_repeat(' ', $len); + } + + for ($i = 0; isset($messages[$i]); ++$i) { + $messages[$i] = sprintf('<%s>%s', $style, $messages[$i], $style); + } + + return implode("\n", $messages); + } + + /** + * Truncates a message to the given length. + * + * @param string $message + * @param int $length + * @param string $suffix + * + * @return string + */ + public function truncate($message, $length, $suffix = '...') + { + $computedLength = $length - $this->strlen($suffix); + + if ($computedLength > $this->strlen($message)) { + return $message; + } + + if (false === $encoding = mb_detect_encoding($message, null, true)) { + return substr($message, 0, $length).$suffix; + } + + return mb_substr($message, 0, $length, $encoding).$suffix; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'formatter'; + } +} diff --git a/vendor/symfony/console/Helper/Helper.php b/vendor/symfony/console/Helper/Helper.php new file mode 100644 index 00000000..44bc2da2 --- /dev/null +++ b/vendor/symfony/console/Helper/Helper.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * Helper is the base class for all helper classes. + * + * @author Fabien Potencier + */ +abstract class Helper implements HelperInterface +{ + protected $helperSet = null; + + /** + * Sets the helper set associated with this helper. + * + * @param HelperSet $helperSet A HelperSet instance + */ + public function setHelperSet(HelperSet $helperSet = null) + { + $this->helperSet = $helperSet; + } + + /** + * Gets the helper set associated with this helper. + * + * @return HelperSet|null + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Returns the length of a string, using mb_strwidth if it is available. + * + * @param string $string The string to check its length + * + * @return int The length of the string + */ + public static function strlen($string) + { + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + /** + * Returns the subset of a string, using mb_substr if it is available. + * + * @param string $string String to subset + * @param int $from Start offset + * @param int|null $length Length to read + * + * @return string The string subset + */ + public static function substr($string, $from, $length = null) + { + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return substr($string, $from, $length); + } + + return mb_substr($string, $from, $length, $encoding); + } + + public static function formatTime($secs) + { + static $timeFormats = array( + array(0, '< 1 sec'), + array(1, '1 sec'), + array(2, 'secs', 1), + array(60, '1 min'), + array(120, 'mins', 60), + array(3600, '1 hr'), + array(7200, 'hrs', 3600), + array(86400, '1 day'), + array(172800, 'days', 86400), + ); + + foreach ($timeFormats as $index => $format) { + if ($secs >= $format[0]) { + if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0]) + || $index == count($timeFormats) - 1 + ) { + if (2 == count($format)) { + return $format[1]; + } + + return floor($secs / $format[2]).' '.$format[1]; + } + } + } + } + + public static function formatMemory($memory) + { + if ($memory >= 1024 * 1024 * 1024) { + return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); + } + + if ($memory >= 1024 * 1024) { + return sprintf('%.1f MiB', $memory / 1024 / 1024); + } + + if ($memory >= 1024) { + return sprintf('%d KiB', $memory / 1024); + } + + return sprintf('%d B', $memory); + } + + public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string) + { + return self::strlen(self::removeDecoration($formatter, $string)); + } + + public static function removeDecoration(OutputFormatterInterface $formatter, $string) + { + $isDecorated = $formatter->isDecorated(); + $formatter->setDecorated(false); + // remove <...> formatting + $string = $formatter->format($string); + // remove already formatted characters + $string = preg_replace("/\033\[[^m]*m/", '', $string); + $formatter->setDecorated($isDecorated); + + return $string; + } +} diff --git a/vendor/symfony/console/Helper/HelperInterface.php b/vendor/symfony/console/Helper/HelperInterface.php new file mode 100644 index 00000000..5a923e0a --- /dev/null +++ b/vendor/symfony/console/Helper/HelperInterface.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\Console\Helper; + +/** + * HelperInterface is the interface all helpers must implement. + * + * @author Fabien Potencier + */ +interface HelperInterface +{ + /** + * Sets the helper set associated with this helper. + * + * @param HelperSet $helperSet A HelperSet instance + */ + public function setHelperSet(HelperSet $helperSet = null); + + /** + * Gets the helper set associated with this helper. + * + * @return HelperSet A HelperSet instance + */ + public function getHelperSet(); + + /** + * Returns the canonical name of this helper. + * + * @return string The canonical name + */ + public function getName(); +} diff --git a/vendor/symfony/console/Helper/HelperSet.php b/vendor/symfony/console/Helper/HelperSet.php new file mode 100644 index 00000000..6f12b39d --- /dev/null +++ b/vendor/symfony/console/Helper/HelperSet.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * HelperSet represents a set of helpers to be used with a command. + * + * @author Fabien Potencier + */ +class HelperSet implements \IteratorAggregate +{ + /** + * @var Helper[] + */ + private $helpers = array(); + private $command; + + /** + * Constructor. + * + * @param Helper[] $helpers An array of helper + */ + public function __construct(array $helpers = array()) + { + foreach ($helpers as $alias => $helper) { + $this->set($helper, is_int($alias) ? null : $alias); + } + } + + /** + * Sets a helper. + * + * @param HelperInterface $helper The helper instance + * @param string $alias An alias + */ + public function set(HelperInterface $helper, $alias = null) + { + $this->helpers[$helper->getName()] = $helper; + if (null !== $alias) { + $this->helpers[$alias] = $helper; + } + + $helper->setHelperSet($this); + } + + /** + * Returns true if the helper if defined. + * + * @param string $name The helper name + * + * @return bool true if the helper is defined, false otherwise + */ + public function has($name) + { + return isset($this->helpers[$name]); + } + + /** + * Gets a helper value. + * + * @param string $name The helper name + * + * @return HelperInterface The helper instance + * + * @throws InvalidArgumentException if the helper is not defined + */ + public function get($name) + { + if (!$this->has($name)) { + throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); + } + + return $this->helpers[$name]; + } + + /** + * Sets the command associated with this helper set. + * + * @param Command $command A Command instance + */ + public function setCommand(Command $command = null) + { + $this->command = $command; + } + + /** + * Gets the command associated with this helper set. + * + * @return Command A Command instance + */ + public function getCommand() + { + return $this->command; + } + + /** + * @return Helper[] + */ + public function getIterator() + { + return new \ArrayIterator($this->helpers); + } +} diff --git a/vendor/symfony/console/Helper/InputAwareHelper.php b/vendor/symfony/console/Helper/InputAwareHelper.php new file mode 100644 index 00000000..42617674 --- /dev/null +++ b/vendor/symfony/console/Helper/InputAwareHelper.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\Console\Helper; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputAwareInterface; + +/** + * An implementation of InputAwareInterface for Helpers. + * + * @author Wouter J + */ +abstract class InputAwareHelper extends Helper implements InputAwareInterface +{ + protected $input; + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + } +} diff --git a/vendor/symfony/console/Helper/ProcessHelper.php b/vendor/symfony/console/Helper/ProcessHelper.php new file mode 100644 index 00000000..2c46a2c3 --- /dev/null +++ b/vendor/symfony/console/Helper/ProcessHelper.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\Console\Helper; + +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Process; +use Symfony\Component\Process\ProcessBuilder; + +/** + * The ProcessHelper class provides helpers to run external processes. + * + * @author Fabien Potencier + */ +class ProcessHelper extends Helper +{ + /** + * Runs an external process. + * + * @param OutputInterface $output An OutputInterface instance + * @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run or a command to run + * @param string|null $error An error message that must be displayed if something went wrong + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * @param int $verbosity The threshold for verbosity + * + * @return Process The process that ran + */ + public function run(OutputInterface $output, $cmd, $error = null, callable $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + if (is_array($cmd)) { + $process = ProcessBuilder::create($cmd)->getProcess(); + } elseif ($cmd instanceof Process) { + $process = $cmd; + } else { + $process = new Process($cmd); + } + + if ($verbosity <= $output->getVerbosity()) { + $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine()))); + } + + if ($output->isDebug()) { + $callback = $this->wrapCallback($output, $process, $callback); + } + + $process->run($callback); + + if ($verbosity <= $output->getVerbosity()) { + $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); + $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); + } + + if (!$process->isSuccessful() && null !== $error) { + $output->writeln(sprintf('%s', $this->escapeString($error))); + } + + return $process; + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @param OutputInterface $output An OutputInterface instance + * @param string|Process $cmd An instance of Process or a command to run + * @param string|null $error An error message that must be displayed if something went wrong + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return Process The process that ran + * + * @throws ProcessFailedException + * + * @see run() + */ + public function mustRun(OutputInterface $output, $cmd, $error = null, callable $callback = null) + { + $process = $this->run($output, $cmd, $error, $callback); + + if (!$process->isSuccessful()) { + throw new ProcessFailedException($process); + } + + return $process; + } + + /** + * Wraps a Process callback to add debugging output. + * + * @param OutputInterface $output An OutputInterface interface + * @param Process $process The Process + * @param callable|null $callback A PHP callable + * + * @return callable + */ + public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + return function ($type, $buffer) use ($output, $process, $callback, $formatter) { + $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type)); + + if (null !== $callback) { + call_user_func($callback, $type, $buffer); + } + }; + } + + private function escapeString($str) + { + return str_replace('<', '\\<', $str); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'process'; + } +} diff --git a/vendor/symfony/console/Helper/ProgressBar.php b/vendor/symfony/console/Helper/ProgressBar.php new file mode 100644 index 00000000..31c49ef7 --- /dev/null +++ b/vendor/symfony/console/Helper/ProgressBar.php @@ -0,0 +1,622 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Terminal; + +/** + * The ProgressBar provides helpers to display progress output. + * + * @author Fabien Potencier + * @author Chris Jones + */ +final class ProgressBar +{ + // options + private $barWidth = 28; + private $barChar; + private $emptyBarChar = '-'; + private $progressChar = '>'; + private $format; + private $internalFormat; + private $redrawFreq = 1; + + /** + * @var OutputInterface + */ + private $output; + private $step = 0; + private $max; + private $startTime; + private $stepWidth; + private $percent = 0.0; + private $formatLineCount; + private $messages = array(); + private $overwrite = true; + private $terminal; + private $firstRun = true; + + private static $formatters; + private static $formats; + + /** + * @param OutputInterface $output An OutputInterface instance + * @param int $max Maximum steps (0 if unknown) + */ + public function __construct(OutputInterface $output, $max = 0) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $this->output = $output; + $this->setMaxSteps($max); + $this->terminal = new Terminal(); + + if (!$this->output->isDecorated()) { + // disable overwrite when output does not support ANSI codes. + $this->overwrite = false; + + // set a reasonable redraw frequency so output isn't flooded + $this->setRedrawFrequency($max / 10); + } + + $this->startTime = time(); + } + + /** + * Sets a placeholder formatter for a given name. + * + * This method also allow you to override an existing placeholder. + * + * @param string $name The placeholder name (including the delimiter char like %) + * @param callable $callable A PHP callable + */ + public static function setPlaceholderFormatterDefinition($name, callable $callable) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + self::$formatters[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name. + * + * @param string $name The placeholder name (including the delimiter char like %) + * + * @return callable|null A PHP callable + */ + public static function getPlaceholderFormatterDefinition($name) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; + } + + /** + * Sets a format for a given name. + * + * This method also allow you to override an existing format. + * + * @param string $name The format name + * @param string $format A format string + */ + public static function setFormatDefinition($name, $format) + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + self::$formats[$name] = $format; + } + + /** + * Gets the format for a given name. + * + * @param string $name The format name + * + * @return string|null A format string + */ + public static function getFormatDefinition($name) + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + return isset(self::$formats[$name]) ? self::$formats[$name] : null; + } + + /** + * Associates a text with a named placeholder. + * + * The text is displayed when the progress bar is rendered but only + * when the corresponding placeholder is part of the custom format line + * (by wrapping the name with %). + * + * @param string $message The text to associate with the placeholder + * @param string $name The name of the placeholder + */ + public function setMessage($message, $name = 'message') + { + $this->messages[$name] = $message; + } + + public function getMessage($name = 'message') + { + return $this->messages[$name]; + } + + /** + * Gets the progress bar start time. + * + * @return int The progress bar start time + */ + public function getStartTime() + { + return $this->startTime; + } + + /** + * Gets the progress bar maximal steps. + * + * @return int The progress bar max steps + */ + public function getMaxSteps() + { + return $this->max; + } + + /** + * Gets the current step position. + * + * @return int The progress bar step + */ + public function getProgress() + { + return $this->step; + } + + /** + * Gets the progress bar step width. + * + * @return int The progress bar step width + */ + private function getStepWidth() + { + return $this->stepWidth; + } + + /** + * Gets the current progress bar percent. + * + * @return float The current progress bar percent + */ + public function getProgressPercent() + { + return $this->percent; + } + + /** + * Sets the progress bar width. + * + * @param int $size The progress bar size + */ + public function setBarWidth($size) + { + $this->barWidth = max(1, (int) $size); + } + + /** + * Gets the progress bar width. + * + * @return int The progress bar size + */ + public function getBarWidth() + { + return $this->barWidth; + } + + /** + * Sets the bar character. + * + * @param string $char A character + */ + public function setBarCharacter($char) + { + $this->barChar = $char; + } + + /** + * Gets the bar character. + * + * @return string A character + */ + public function getBarCharacter() + { + if (null === $this->barChar) { + return $this->max ? '=' : $this->emptyBarChar; + } + + return $this->barChar; + } + + /** + * Sets the empty bar character. + * + * @param string $char A character + */ + public function setEmptyBarCharacter($char) + { + $this->emptyBarChar = $char; + } + + /** + * Gets the empty bar character. + * + * @return string A character + */ + public function getEmptyBarCharacter() + { + return $this->emptyBarChar; + } + + /** + * Sets the progress bar character. + * + * @param string $char A character + */ + public function setProgressCharacter($char) + { + $this->progressChar = $char; + } + + /** + * Gets the progress bar character. + * + * @return string A character + */ + public function getProgressCharacter() + { + return $this->progressChar; + } + + /** + * Sets the progress bar format. + * + * @param string $format The format + */ + public function setFormat($format) + { + $this->format = null; + $this->internalFormat = $format; + } + + /** + * Sets the redraw frequency. + * + * @param int|float $freq The frequency in steps + */ + public function setRedrawFrequency($freq) + { + $this->redrawFreq = max((int) $freq, 1); + } + + /** + * Starts the progress output. + * + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged + */ + public function start($max = null) + { + $this->startTime = time(); + $this->step = 0; + $this->percent = 0.0; + + if (null !== $max) { + $this->setMaxSteps($max); + } + + $this->display(); + } + + /** + * Advances the progress output X steps. + * + * @param int $step Number of steps to advance + */ + public function advance($step = 1) + { + $this->setProgress($this->step + $step); + } + + /** + * Sets whether to overwrite the progressbar, false for new line. + * + * @param bool $overwrite + */ + public function setOverwrite($overwrite) + { + $this->overwrite = (bool) $overwrite; + } + + /** + * Sets the current progress. + * + * @param int $step The current progress + */ + public function setProgress($step) + { + $step = (int) $step; + + if ($this->max && $step > $this->max) { + $this->max = $step; + } elseif ($step < 0) { + $step = 0; + } + + $prevPeriod = (int) ($this->step / $this->redrawFreq); + $currPeriod = (int) ($step / $this->redrawFreq); + $this->step = $step; + $this->percent = $this->max ? (float) $this->step / $this->max : 0; + if ($prevPeriod !== $currPeriod || $this->max === $step) { + $this->display(); + } + } + + /** + * Finishes the progress output. + */ + public function finish() + { + if (!$this->max) { + $this->max = $this->step; + } + + if ($this->step === $this->max && !$this->overwrite) { + // prevent double 100% output + return; + } + + $this->setProgress($this->max); + } + + /** + * Outputs the current progress string. + */ + public function display() + { + if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + if (null === $this->format) { + $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); + } + + $this->overwrite($this->buildLine()); + } + + /** + * Removes the progress bar from the current line. + * + * This is useful if you wish to write some output + * while a progress bar is running. + * Call display() to show the progress bar again. + */ + public function clear() + { + if (!$this->overwrite) { + return; + } + + if (null === $this->format) { + $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); + } + + $this->overwrite(''); + } + + /** + * Sets the progress bar format. + * + * @param string $format The format + */ + private function setRealFormat($format) + { + // try to use the _nomax variant if available + if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) { + $this->format = self::getFormatDefinition($format.'_nomax'); + } elseif (null !== self::getFormatDefinition($format)) { + $this->format = self::getFormatDefinition($format); + } else { + $this->format = $format; + } + + $this->formatLineCount = substr_count($this->format, "\n"); + } + + /** + * Sets the progress bar maximal steps. + * + * @param int $max The progress bar max steps + */ + private function setMaxSteps($max) + { + $this->max = max(0, (int) $max); + $this->stepWidth = $this->max ? Helper::strlen($this->max) : 4; + } + + /** + * Overwrites a previous message to the output. + * + * @param string $message The message + */ + private function overwrite($message) + { + if ($this->overwrite) { + if (!$this->firstRun) { + // Move the cursor to the beginning of the line + $this->output->write("\x0D"); + + // Erase the line + $this->output->write("\x1B[2K"); + + // Erase previous lines + if ($this->formatLineCount > 0) { + $this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount)); + } + } + } elseif ($this->step > 0) { + $this->output->writeln(''); + } + + $this->firstRun = false; + + $this->output->write($message); + } + + private function determineBestFormat() + { + switch ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + case OutputInterface::VERBOSITY_VERBOSE: + return $this->max ? 'verbose' : 'verbose_nomax'; + case OutputInterface::VERBOSITY_VERY_VERBOSE: + return $this->max ? 'very_verbose' : 'very_verbose_nomax'; + case OutputInterface::VERBOSITY_DEBUG: + return $this->max ? 'debug' : 'debug_nomax'; + default: + return $this->max ? 'normal' : 'normal_nomax'; + } + } + + private static function initPlaceholderFormatters() + { + return array( + 'bar' => function (ProgressBar $bar, OutputInterface $output) { + $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth()); + $display = str_repeat($bar->getBarCharacter(), $completeBars); + if ($completeBars < $bar->getBarWidth()) { + $emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter()); + $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars); + } + + return $display; + }, + 'elapsed' => function (ProgressBar $bar) { + return Helper::formatTime(time() - $bar->getStartTime()); + }, + 'remaining' => function (ProgressBar $bar) { + if (!$bar->getMaxSteps()) { + throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); + } + + if (!$bar->getProgress()) { + $remaining = 0; + } else { + $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress())); + } + + return Helper::formatTime($remaining); + }, + 'estimated' => function (ProgressBar $bar) { + if (!$bar->getMaxSteps()) { + throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); + } + + if (!$bar->getProgress()) { + $estimated = 0; + } else { + $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps()); + } + + return Helper::formatTime($estimated); + }, + 'memory' => function (ProgressBar $bar) { + return Helper::formatMemory(memory_get_usage(true)); + }, + 'current' => function (ProgressBar $bar) { + return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT); + }, + 'max' => function (ProgressBar $bar) { + return $bar->getMaxSteps(); + }, + 'percent' => function (ProgressBar $bar) { + return floor($bar->getProgressPercent() * 100); + }, + ); + } + + private static function initFormats() + { + return array( + 'normal' => ' %current%/%max% [%bar%] %percent:3s%%', + 'normal_nomax' => ' %current% [%bar%]', + + 'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', + 'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', + + 'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', + 'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', + + 'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', + 'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%', + ); + } + + /** + * @return string + */ + private function buildLine() + { + $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; + $callback = function ($matches) { + if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) { + $text = call_user_func($formatter, $this, $this->output); + } elseif (isset($this->messages[$matches[1]])) { + $text = $this->messages[$matches[1]]; + } else { + return $matches[0]; + } + + if (isset($matches[2])) { + $text = sprintf('%'.$matches[2], $text); + } + + return $text; + }; + $line = preg_replace_callback($regex, $callback, $this->format); + + // gets string length for each sub line with multiline format + $linesLength = array_map(function ($subLine) { + return Helper::strlenWithoutDecoration($this->output->getFormatter(), rtrim($subLine, "\r")); + }, explode("\n", $line)); + + $linesWidth = max($linesLength); + + $terminalWidth = $this->terminal->getWidth(); + if ($linesWidth <= $terminalWidth) { + return $line; + } + + $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth); + + return preg_replace_callback($regex, $callback, $this->format); + } +} diff --git a/vendor/symfony/console/Helper/ProgressIndicator.php b/vendor/symfony/console/Helper/ProgressIndicator.php new file mode 100644 index 00000000..d441accd --- /dev/null +++ b/vendor/symfony/console/Helper/ProgressIndicator.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\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Kevin Bond + */ +class ProgressIndicator +{ + private $output; + private $startTime; + private $format; + private $message; + private $indicatorValues; + private $indicatorCurrent; + private $indicatorChangeInterval; + private $indicatorUpdateTime; + private $started = false; + + private static $formatters; + private static $formats; + + /** + * @param OutputInterface $output + * @param string|null $format Indicator format + * @param int $indicatorChangeInterval Change interval in milliseconds + * @param array|null $indicatorValues Animated indicator characters + */ + public function __construct(OutputInterface $output, $format = null, $indicatorChangeInterval = 100, $indicatorValues = null) + { + $this->output = $output; + + if (null === $format) { + $format = $this->determineBestFormat(); + } + + if (null === $indicatorValues) { + $indicatorValues = array('-', '\\', '|', '/'); + } + + $indicatorValues = array_values($indicatorValues); + + if (2 > count($indicatorValues)) { + throw new InvalidArgumentException('Must have at least 2 indicator value characters.'); + } + + $this->format = self::getFormatDefinition($format); + $this->indicatorChangeInterval = $indicatorChangeInterval; + $this->indicatorValues = $indicatorValues; + $this->startTime = time(); + } + + /** + * Sets the current indicator message. + * + * @param string|null $message + */ + public function setMessage($message) + { + $this->message = $message; + + $this->display(); + } + + /** + * Starts the indicator output. + * + * @param $message + */ + public function start($message) + { + if ($this->started) { + throw new LogicException('Progress indicator already started.'); + } + + $this->message = $message; + $this->started = true; + $this->startTime = time(); + $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval; + $this->indicatorCurrent = 0; + + $this->display(); + } + + /** + * Advances the indicator. + */ + public function advance() + { + if (!$this->started) { + throw new LogicException('Progress indicator has not yet been started.'); + } + + if (!$this->output->isDecorated()) { + return; + } + + $currentTime = $this->getCurrentTimeInMilliseconds(); + + if ($currentTime < $this->indicatorUpdateTime) { + return; + } + + $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval; + ++$this->indicatorCurrent; + + $this->display(); + } + + /** + * Finish the indicator with message. + * + * @param $message + */ + public function finish($message) + { + if (!$this->started) { + throw new LogicException('Progress indicator has not yet been started.'); + } + + $this->message = $message; + $this->display(); + $this->output->writeln(''); + $this->started = false; + } + + /** + * Gets the format for a given name. + * + * @param string $name The format name + * + * @return string|null A format string + */ + public static function getFormatDefinition($name) + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + return isset(self::$formats[$name]) ? self::$formats[$name] : null; + } + + /** + * Sets a placeholder formatter for a given name. + * + * This method also allow you to override an existing placeholder. + * + * @param string $name The placeholder name (including the delimiter char like %) + * @param callable $callable A PHP callable + */ + public static function setPlaceholderFormatterDefinition($name, $callable) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + self::$formatters[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name. + * + * @param string $name The placeholder name (including the delimiter char like %) + * + * @return callable|null A PHP callable + */ + public static function getPlaceholderFormatterDefinition($name) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; + } + + private function display() + { + if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + $self = $this; + + $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self) { + if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) { + return call_user_func($formatter, $self); + } + + return $matches[0]; + }, $this->format)); + } + + private function determineBestFormat() + { + switch ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + case OutputInterface::VERBOSITY_VERBOSE: + return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi'; + case OutputInterface::VERBOSITY_VERY_VERBOSE: + case OutputInterface::VERBOSITY_DEBUG: + return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi'; + default: + return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi'; + } + } + + /** + * Overwrites a previous message to the output. + * + * @param string $message The message + */ + private function overwrite($message) + { + if ($this->output->isDecorated()) { + $this->output->write("\x0D\x1B[2K"); + $this->output->write($message); + } else { + $this->output->writeln($message); + } + } + + private function getCurrentTimeInMilliseconds() + { + return round(microtime(true) * 1000); + } + + private static function initPlaceholderFormatters() + { + return array( + 'indicator' => function (ProgressIndicator $indicator) { + return $indicator->indicatorValues[$indicator->indicatorCurrent % count($indicator->indicatorValues)]; + }, + 'message' => function (ProgressIndicator $indicator) { + return $indicator->message; + }, + 'elapsed' => function (ProgressIndicator $indicator) { + return Helper::formatTime(time() - $indicator->startTime); + }, + 'memory' => function () { + return Helper::formatMemory(memory_get_usage(true)); + }, + ); + } + + private static function initFormats() + { + return array( + 'normal' => ' %indicator% %message%', + 'normal_no_ansi' => ' %message%', + + 'verbose' => ' %indicator% %message% (%elapsed:6s%)', + 'verbose_no_ansi' => ' %message% (%elapsed:6s%)', + + 'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)', + 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)', + ); + } +} diff --git a/vendor/symfony/console/Helper/QuestionHelper.php b/vendor/symfony/console/Helper/QuestionHelper.php new file mode 100644 index 00000000..dd89105f --- /dev/null +++ b/vendor/symfony/console/Helper/QuestionHelper.php @@ -0,0 +1,474 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\StreamableInputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Question\ChoiceQuestion; + +/** + * The QuestionHelper class provides helpers to interact with the user. + * + * @author Fabien Potencier + */ +class QuestionHelper extends Helper +{ + private $inputStream; + private static $shell; + private static $stty; + + /** + * Asks a question to the user. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * @param Question $question The question to ask + * + * @return mixed The user answer + * + * @throws RuntimeException If there is no data to read in the input stream + */ + public function ask(InputInterface $input, OutputInterface $output, Question $question) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + if (!$input->isInteractive()) { + return $question->getDefault(); + } + + if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) { + $this->inputStream = $stream; + } + + if (!$question->getValidator()) { + return $this->doAsk($output, $question); + } + + $interviewer = function () use ($output, $question) { + return $this->doAsk($output, $question); + }; + + return $this->validateAttempts($interviewer, $output, $question); + } + + /** + * Sets the input stream to read from when interacting with the user. + * + * This is mainly useful for testing purpose. + * + * @deprecated since version 3.2, to be removed in 4.0. Use + * StreamableInputInterface::setStream() instead. + * + * @param resource $stream The input stream + * + * @throws InvalidArgumentException In case the stream is not a resource + */ + public function setInputStream($stream) + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.2 and will be removed in 4.0. Use %s::setStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED); + + if (!is_resource($stream)) { + throw new InvalidArgumentException('Input stream must be a valid resource.'); + } + + $this->inputStream = $stream; + } + + /** + * Returns the helper's input stream. + * + * @deprecated since version 3.2, to be removed in 4.0. Use + * StreamableInputInterface::getStream() instead. + * + * @return resource + */ + public function getInputStream() + { + if (0 === func_num_args() || func_get_arg(0)) { + @trigger_error(sprintf('The %s() method is deprecated since version 3.2 and will be removed in 4.0. Use %s::getStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED); + } + + return $this->inputStream; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'question'; + } + + /** + * Prevents usage of stty. + */ + public static function disableStty() + { + self::$stty = false; + } + + /** + * Asks the question to the user. + * + * @param OutputInterface $output + * @param Question $question + * + * @return bool|mixed|null|string + * + * @throws \Exception + * @throws \RuntimeException + */ + private function doAsk(OutputInterface $output, Question $question) + { + $this->writePrompt($output, $question); + + $inputStream = $this->inputStream ?: STDIN; + $autocomplete = $question->getAutocompleterValues(); + + if (null === $autocomplete || !$this->hasSttyAvailable()) { + $ret = false; + if ($question->isHidden()) { + try { + $ret = trim($this->getHiddenResponse($output, $inputStream)); + } catch (\RuntimeException $e) { + if (!$question->isHiddenFallback()) { + throw $e; + } + } + } + + if (false === $ret) { + $ret = fgets($inputStream, 4096); + if (false === $ret) { + throw new RuntimeException('Aborted'); + } + $ret = trim($ret); + } + } else { + $ret = trim($this->autocomplete($output, $question, $inputStream)); + } + + $ret = strlen($ret) > 0 ? $ret : $question->getDefault(); + + if ($normalizer = $question->getNormalizer()) { + return $normalizer($ret); + } + + return $ret; + } + + /** + * Outputs the question prompt. + * + * @param OutputInterface $output + * @param Question $question + */ + protected function writePrompt(OutputInterface $output, Question $question) + { + $message = $question->getQuestion(); + + if ($question instanceof ChoiceQuestion) { + $maxWidth = max(array_map(array($this, 'strlen'), array_keys($question->getChoices()))); + + $messages = (array) $question->getQuestion(); + foreach ($question->getChoices() as $key => $value) { + $width = $maxWidth - $this->strlen($key); + $messages[] = ' ['.$key.str_repeat(' ', $width).'] '.$value; + } + + $output->writeln($messages); + + $message = $question->getPrompt(); + } + + $output->write($message); + } + + /** + * Outputs an error message. + * + * @param OutputInterface $output + * @param \Exception $error + */ + protected function writeError(OutputInterface $output, \Exception $error) + { + if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { + $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'); + } else { + $message = ''.$error->getMessage().''; + } + + $output->writeln($message); + } + + /** + * Autocompletes a question. + * + * @param OutputInterface $output + * @param Question $question + * @param resource $inputStream + * + * @return string + */ + private function autocomplete(OutputInterface $output, Question $question, $inputStream) + { + $autocomplete = $question->getAutocompleterValues(); + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + + $sttyMode = shell_exec('stty -g'); + + // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) + shell_exec('stty -icanon -echo'); + + // Add highlighted text style + $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); + + // Read a keypress + while (!feof($inputStream)) { + $c = fread($inputStream, 1); + + // Backspace Character + if ("\177" === $c) { + if (0 === $numMatches && 0 !== $i) { + --$i; + // Move cursor backwards + $output->write("\033[1D"); + } + + if ($i === 0) { + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + } else { + $numMatches = 0; + } + + // Pop the last character off the end of our string + $ret = substr($ret, 0, $i); + } elseif ("\033" === $c) { + // Did we read an escape sequence? + $c .= fread($inputStream, 2); + + // A = Up Arrow. B = Down Arrow + if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = $matches[$ofs]; + // Echo out remaining chars for current match + $output->write(substr($ret, $i)); + $i = strlen($ret); + } + + if ("\n" === $c) { + $output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + $output->write($c); + $ret .= $c; + ++$i; + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete as $value) { + // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) + if (0 === strpos($value, $ret) && $i !== strlen($value)) { + $matches[$numMatches++] = $value; + } + } + } + + // Erase characters from cursor to end of line + $output->write("\033[K"); + + if ($numMatches > 0 && -1 !== $ofs) { + // Save cursor position + $output->write("\0337"); + // Write highlighted text + $output->write(''.substr($matches[$ofs], $i).''); + // Restore cursor position + $output->write("\0338"); + } + } + + // Reset stty so it behaves normally again + shell_exec(sprintf('stty %s', $sttyMode)); + + return $ret; + } + + /** + * Gets a hidden response from user. + * + * @param OutputInterface $output An Output instance + * @param resource $inputStream The handler resource + * + * @return string The answer + * + * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden + */ + private function getHiddenResponse(OutputInterface $output, $inputStream) + { + if ('\\' === DIRECTORY_SEPARATOR) { + $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; + + // handle code running from a phar + if ('phar:' === substr(__FILE__, 0, 5)) { + $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; + copy($exe, $tmpExe); + $exe = $tmpExe; + } + + $value = rtrim(shell_exec($exe)); + $output->writeln(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + return $value; + } + + if ($this->hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -echo'); + $value = fgets($inputStream, 4096); + shell_exec(sprintf('stty %s', $sttyMode)); + + if (false === $value) { + throw new RuntimeException('Aborted'); + } + + $value = trim($value); + $output->writeln(''); + + return $value; + } + + if (false !== $shell = $this->getShell()) { + $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword'; + $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); + $value = rtrim(shell_exec($command)); + $output->writeln(''); + + return $value; + } + + throw new RuntimeException('Unable to hide the response.'); + } + + /** + * Validates an attempt. + * + * @param callable $interviewer A callable that will ask for a question and return the result + * @param OutputInterface $output An Output instance + * @param Question $question A Question instance + * + * @return mixed The validated response + * + * @throws \Exception In case the max number of attempts has been reached and no valid response has been given + */ + private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question) + { + $error = null; + $attempts = $question->getMaxAttempts(); + while (null === $attempts || $attempts--) { + if (null !== $error) { + $this->writeError($output, $error); + } + + try { + return call_user_func($question->getValidator(), $interviewer()); + } catch (RuntimeException $e) { + throw $e; + } catch (\Exception $error) { + } + } + + throw $error; + } + + /** + * Returns a valid unix shell. + * + * @return string|bool The valid shell name, false in case no valid shell is found + */ + private function getShell() + { + if (null !== self::$shell) { + return self::$shell; + } + + self::$shell = false; + + if (file_exists('/usr/bin/env')) { + // handle other OSs with bash/zsh/ksh/csh if available to hide the answer + $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; + foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) { + if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { + self::$shell = $sh; + break; + } + } + } + + return self::$shell; + } + + /** + * Returns whether Stty is available or not. + * + * @return bool + */ + private function hasSttyAvailable() + { + if (null !== self::$stty) { + return self::$stty; + } + + exec('stty 2>&1', $output, $exitcode); + + return self::$stty = $exitcode === 0; + } +} diff --git a/vendor/symfony/console/Helper/SymfonyQuestionHelper.php b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php new file mode 100644 index 00000000..25e094a0 --- /dev/null +++ b/vendor/symfony/console/Helper/SymfonyQuestionHelper.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\Console\Helper; + +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * Symfony Style Guide compliant question helper. + * + * @author Kevin Bond + */ +class SymfonyQuestionHelper extends QuestionHelper +{ + /** + * {@inheritdoc} + */ + public function ask(InputInterface $input, OutputInterface $output, Question $question) + { + $validator = $question->getValidator(); + $question->setValidator(function ($value) use ($validator) { + if (null !== $validator) { + $value = $validator($value); + } else { + // make required + if (!is_array($value) && !is_bool($value) && 0 === strlen($value)) { + throw new LogicException('A value is required.'); + } + } + + return $value; + }); + + return parent::ask($input, $output, $question); + } + + /** + * {@inheritdoc} + */ + protected function writePrompt(OutputInterface $output, Question $question) + { + $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); + $default = $question->getDefault(); + + switch (true) { + case null === $default: + $text = sprintf(' %s:', $text); + + break; + + case $question instanceof ConfirmationQuestion: + $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); + + break; + + case $question instanceof ChoiceQuestion && $question->isMultiselect(): + $choices = $question->getChoices(); + $default = explode(',', $default); + + foreach ($default as $key => $value) { + $default[$key] = $choices[trim($value)]; + } + + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape(implode(', ', $default))); + + break; + + case $question instanceof ChoiceQuestion: + $choices = $question->getChoices(); + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($choices[$default])); + + break; + + default: + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($default)); + } + + $output->writeln($text); + + if ($question instanceof ChoiceQuestion) { + $width = max(array_map('strlen', array_keys($question->getChoices()))); + + foreach ($question->getChoices() as $key => $value) { + $output->writeln(sprintf(" [%-${width}s] %s", $key, $value)); + } + } + + $output->write(' > '); + } + + /** + * {@inheritdoc} + */ + protected function writeError(OutputInterface $output, \Exception $error) + { + if ($output instanceof SymfonyStyle) { + $output->newLine(); + $output->error($error->getMessage()); + + return; + } + + parent::writeError($output, $error); + } +} diff --git a/vendor/symfony/console/Helper/Table.php b/vendor/symfony/console/Helper/Table.php new file mode 100644 index 00000000..574e9b46 --- /dev/null +++ b/vendor/symfony/console/Helper/Table.php @@ -0,0 +1,709 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * Provides helpers to display a table. + * + * @author Fabien Potencier + * @author Саша Стаменковић + * @author Abdellatif Ait boudad + * @author Max Grigorian + */ +class Table +{ + /** + * Table headers. + * + * @var array + */ + private $headers = array(); + + /** + * Table rows. + * + * @var array + */ + private $rows = array(); + + /** + * Column widths cache. + * + * @var array + */ + private $effectiveColumnWidths = array(); + + /** + * Number of columns cache. + * + * @var array + */ + private $numberOfColumns; + + /** + * @var OutputInterface + */ + private $output; + + /** + * @var TableStyle + */ + private $style; + + /** + * @var array + */ + private $columnStyles = array(); + + /** + * User set column widths. + * + * @var array + */ + private $columnWidths = array(); + + private static $styles; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + $this->setStyle('default'); + } + + /** + * Sets a style definition. + * + * @param string $name The style name + * @param TableStyle $style A TableStyle instance + */ + public static function setStyleDefinition($name, TableStyle $style) + { + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + self::$styles[$name] = $style; + } + + /** + * Gets a style definition by name. + * + * @param string $name The style name + * + * @return TableStyle + */ + public static function getStyleDefinition($name) + { + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + if (isset(self::$styles[$name])) { + return self::$styles[$name]; + } + + throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } + + /** + * Sets table style. + * + * @param TableStyle|string $name The style name or a TableStyle instance + * + * @return $this + */ + public function setStyle($name) + { + $this->style = $this->resolveStyle($name); + + return $this; + } + + /** + * Gets the current table style. + * + * @return TableStyle + */ + public function getStyle() + { + return $this->style; + } + + /** + * Sets table column style. + * + * @param int $columnIndex Column index + * @param TableStyle|string $name The style name or a TableStyle instance + * + * @return $this + */ + public function setColumnStyle($columnIndex, $name) + { + $columnIndex = (int) $columnIndex; + + $this->columnStyles[$columnIndex] = $this->resolveStyle($name); + + return $this; + } + + /** + * Gets the current style for a column. + * + * If style was not set, it returns the global table style. + * + * @param int $columnIndex Column index + * + * @return TableStyle + */ + public function getColumnStyle($columnIndex) + { + if (isset($this->columnStyles[$columnIndex])) { + return $this->columnStyles[$columnIndex]; + } + + return $this->getStyle(); + } + + /** + * Sets the minimum width of a column. + * + * @param int $columnIndex Column index + * @param int $width Minimum column width in characters + * + * @return $this + */ + public function setColumnWidth($columnIndex, $width) + { + $this->columnWidths[(int) $columnIndex] = (int) $width; + + return $this; + } + + /** + * Sets the minimum width of all columns. + * + * @param array $widths + * + * @return $this + */ + public function setColumnWidths(array $widths) + { + $this->columnWidths = array(); + foreach ($widths as $index => $width) { + $this->setColumnWidth($index, $width); + } + + return $this; + } + + public function setHeaders(array $headers) + { + $headers = array_values($headers); + if (!empty($headers) && !is_array($headers[0])) { + $headers = array($headers); + } + + $this->headers = $headers; + + return $this; + } + + public function setRows(array $rows) + { + $this->rows = array(); + + return $this->addRows($rows); + } + + public function addRows(array $rows) + { + foreach ($rows as $row) { + $this->addRow($row); + } + + return $this; + } + + public function addRow($row) + { + if ($row instanceof TableSeparator) { + $this->rows[] = $row; + + return $this; + } + + if (!is_array($row)) { + throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.'); + } + + $this->rows[] = array_values($row); + + return $this; + } + + public function setRow($column, array $row) + { + $this->rows[$column] = $row; + + return $this; + } + + /** + * Renders table to output. + * + * Example: + * +---------------+-----------------------+------------------+ + * | ISBN | Title | Author | + * +---------------+-----------------------+------------------+ + * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | + * +---------------+-----------------------+------------------+ + */ + public function render() + { + $this->calculateNumberOfColumns(); + $rows = $this->buildTableRows($this->rows); + $headers = $this->buildTableRows($this->headers); + + $this->calculateColumnsWidth(array_merge($headers, $rows)); + + $this->renderRowSeparator(); + if (!empty($headers)) { + foreach ($headers as $header) { + $this->renderRow($header, $this->style->getCellHeaderFormat()); + $this->renderRowSeparator(); + } + } + foreach ($rows as $row) { + if ($row instanceof TableSeparator) { + $this->renderRowSeparator(); + } else { + $this->renderRow($row, $this->style->getCellRowFormat()); + } + } + if (!empty($rows)) { + $this->renderRowSeparator(); + } + + $this->cleanup(); + } + + /** + * Renders horizontal header separator. + * + * Example: +-----+-----------+-------+ + */ + private function renderRowSeparator() + { + if (0 === $count = $this->numberOfColumns) { + return; + } + + if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) { + return; + } + + $markup = $this->style->getCrossingChar(); + for ($column = 0; $column < $count; ++$column) { + $markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->effectiveColumnWidths[$column]).$this->style->getCrossingChar(); + } + + $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); + } + + /** + * Renders vertical column separator. + */ + private function renderColumnSeparator() + { + return sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()); + } + + /** + * Renders table row. + * + * Example: | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * + * @param array $row + * @param string $cellFormat + */ + private function renderRow(array $row, $cellFormat) + { + if (empty($row)) { + return; + } + + $rowContent = $this->renderColumnSeparator(); + foreach ($this->getRowColumns($row) as $column) { + $rowContent .= $this->renderCell($row, $column, $cellFormat); + $rowContent .= $this->renderColumnSeparator(); + } + $this->output->writeln($rowContent); + } + + /** + * Renders table cell with padding. + * + * @param array $row + * @param int $column + * @param string $cellFormat + */ + private function renderCell(array $row, $column, $cellFormat) + { + $cell = isset($row[$column]) ? $row[$column] : ''; + $width = $this->effectiveColumnWidths[$column]; + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // add the width of the following columns(numbers of colspan). + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { + $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn]; + } + } + + // str_pad won't work properly with multi-byte strings, we need to fix the padding + if (false !== $encoding = mb_detect_encoding($cell, null, true)) { + $width += strlen($cell) - mb_strwidth($cell, $encoding); + } + + $style = $this->getColumnStyle($column); + + if ($cell instanceof TableSeparator) { + return sprintf($style->getBorderFormat(), str_repeat($style->getHorizontalBorderChar(), $width)); + } + + $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); + $content = sprintf($style->getCellRowContentFormat(), $cell); + + return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType())); + } + + /** + * Calculate number of columns for this table. + */ + private function calculateNumberOfColumns() + { + if (null !== $this->numberOfColumns) { + return; + } + + $columns = array(0); + foreach (array_merge($this->headers, $this->rows) as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + $columns[] = $this->getNumberOfColumns($row); + } + + $this->numberOfColumns = max($columns); + } + + private function buildTableRows($rows) + { + $unmergedRows = array(); + for ($rowKey = 0; $rowKey < count($rows); ++$rowKey) { + $rows = $this->fillNextRows($rows, $rowKey); + + // Remove any new line breaks and replace it with a new line + foreach ($rows[$rowKey] as $column => $cell) { + if (!strstr($cell, "\n")) { + continue; + } + $lines = explode("\n", str_replace("\n", "\n", $cell)); + foreach ($lines as $lineKey => $line) { + if ($cell instanceof TableCell) { + $line = new TableCell($line, array('colspan' => $cell->getColspan())); + } + if (0 === $lineKey) { + $rows[$rowKey][$column] = $line; + } else { + $unmergedRows[$rowKey][$lineKey][$column] = $line; + } + } + } + } + + $tableRows = array(); + foreach ($rows as $rowKey => $row) { + $tableRows[] = $this->fillCells($row); + if (isset($unmergedRows[$rowKey])) { + $tableRows = array_merge($tableRows, $unmergedRows[$rowKey]); + } + } + + return $tableRows; + } + + /** + * fill rows that contains rowspan > 1. + * + * @param array $rows + * @param int $line + * + * @return array + */ + private function fillNextRows($rows, $line) + { + $unmergedRows = array(); + foreach ($rows[$line] as $column => $cell) { + if ($cell instanceof TableCell && $cell->getRowspan() > 1) { + $nbLines = $cell->getRowspan() - 1; + $lines = array($cell); + if (strstr($cell, "\n")) { + $lines = explode("\n", str_replace("\n", "\n", $cell)); + $nbLines = count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; + + $rows[$line][$column] = new TableCell($lines[0], array('colspan' => $cell->getColspan())); + unset($lines[0]); + } + + // create a two dimensional array (rowspan x colspan) + $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, array()), $unmergedRows); + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : ''; + $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, array('colspan' => $cell->getColspan())); + if ($nbLines === $unmergedRowKey - $line) { + break; + } + } + } + } + + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + // we need to know if $unmergedRow will be merged or inserted into $rows + if (isset($rows[$unmergedRowKey]) && is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { + foreach ($unmergedRow as $cellKey => $cell) { + // insert cell into row at cellKey position + array_splice($rows[$unmergedRowKey], $cellKey, 0, array($cell)); + } + } else { + $row = $this->copyRow($rows, $unmergedRowKey - 1); + foreach ($unmergedRow as $column => $cell) { + if (!empty($cell)) { + $row[$column] = $unmergedRow[$column]; + } + } + array_splice($rows, $unmergedRowKey, 0, array($row)); + } + } + + return $rows; + } + + /** + * fill cells for a row that contains colspan > 1. + * + * @param array $row + * + * @return array + */ + private function fillCells($row) + { + $newRow = array(); + foreach ($row as $column => $cell) { + $newRow[] = $cell; + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { + // insert empty value at column position + $newRow[] = ''; + } + } + } + + return $newRow ?: $row; + } + + /** + * @param array $rows + * @param int $line + * + * @return array + */ + private function copyRow($rows, $line) + { + $row = $rows[$line]; + foreach ($row as $cellKey => $cellValue) { + $row[$cellKey] = ''; + if ($cellValue instanceof TableCell) { + $row[$cellKey] = new TableCell('', array('colspan' => $cellValue->getColspan())); + } + } + + return $row; + } + + /** + * Gets number of columns by row. + * + * @param array $row + * + * @return int + */ + private function getNumberOfColumns(array $row) + { + $columns = count($row); + foreach ($row as $column) { + $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; + } + + return $columns; + } + + /** + * Gets list of columns for the given row. + * + * @param array $row + * + * @return array + */ + private function getRowColumns($row) + { + $columns = range(0, $this->numberOfColumns - 1); + foreach ($row as $cellKey => $cell) { + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // exclude grouped columns. + $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); + } + } + + return $columns; + } + + /** + * Calculates columns widths. + * + * @param array $rows + */ + private function calculateColumnsWidth($rows) + { + for ($column = 0; $column < $this->numberOfColumns; ++$column) { + $lengths = array(); + foreach ($rows as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + foreach ($row as $i => $cell) { + if ($cell instanceof TableCell) { + $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); + $textLength = Helper::strlen($textContent); + if ($textLength > 0) { + $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan())); + foreach ($contentColumns as $position => $content) { + $row[$i + $position] = $content; + } + } + } + } + + $lengths[] = $this->getCellWidth($row, $column); + } + + $this->effectiveColumnWidths[$column] = max($lengths) + strlen($this->style->getCellRowContentFormat()) - 2; + } + } + + /** + * Gets column width. + * + * @return int + */ + private function getColumnSeparatorWidth() + { + return strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar())); + } + + /** + * Gets cell width. + * + * @param array $row + * @param int $column + * + * @return int + */ + private function getCellWidth(array $row, $column) + { + $cellWidth = 0; + + if (isset($row[$column])) { + $cell = $row[$column]; + $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); + } + + $columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0; + + return max($cellWidth, $columnWidth); + } + + /** + * Called after rendering to cleanup cache data. + */ + private function cleanup() + { + $this->effectiveColumnWidths = array(); + $this->numberOfColumns = null; + } + + private static function initStyles() + { + $borderless = new TableStyle(); + $borderless + ->setHorizontalBorderChar('=') + ->setVerticalBorderChar(' ') + ->setCrossingChar(' ') + ; + + $compact = new TableStyle(); + $compact + ->setHorizontalBorderChar('') + ->setVerticalBorderChar(' ') + ->setCrossingChar('') + ->setCellRowContentFormat('%s') + ; + + $styleGuide = new TableStyle(); + $styleGuide + ->setHorizontalBorderChar('-') + ->setVerticalBorderChar(' ') + ->setCrossingChar(' ') + ->setCellHeaderFormat('%s') + ; + + return array( + 'default' => new TableStyle(), + 'borderless' => $borderless, + 'compact' => $compact, + 'symfony-style-guide' => $styleGuide, + ); + } + + private function resolveStyle($name) + { + if ($name instanceof TableStyle) { + return $name; + } + + if (isset(self::$styles[$name])) { + return self::$styles[$name]; + } + + throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } +} diff --git a/vendor/symfony/console/Helper/TableCell.php b/vendor/symfony/console/Helper/TableCell.php new file mode 100644 index 00000000..a3064fc0 --- /dev/null +++ b/vendor/symfony/console/Helper/TableCell.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\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Abdellatif Ait boudad + */ +class TableCell +{ + /** + * @var string + */ + private $value; + + /** + * @var array + */ + private $options = array( + 'rowspan' => 1, + 'colspan' => 1, + ); + + /** + * @param string $value + * @param array $options + */ + public function __construct($value = '', array $options = array()) + { + if (is_numeric($value) && !is_string($value)) { + $value = (string) $value; + } + + $this->value = $value; + + // check option names + if ($diff = array_diff(array_keys($options), array_keys($this->options))) { + throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff))); + } + + $this->options = array_merge($this->options, $options); + } + + /** + * Returns the cell value. + * + * @return string + */ + public function __toString() + { + return $this->value; + } + + /** + * Gets number of colspan. + * + * @return int + */ + public function getColspan() + { + return (int) $this->options['colspan']; + } + + /** + * Gets number of rowspan. + * + * @return int + */ + public function getRowspan() + { + return (int) $this->options['rowspan']; + } +} diff --git a/vendor/symfony/console/Helper/TableSeparator.php b/vendor/symfony/console/Helper/TableSeparator.php new file mode 100644 index 00000000..8cc73e69 --- /dev/null +++ b/vendor/symfony/console/Helper/TableSeparator.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\Console\Helper; + +/** + * Marks a row as being a separator. + * + * @author Fabien Potencier + */ +class TableSeparator extends TableCell +{ + /** + * @param array $options + */ + public function __construct(array $options = array()) + { + parent::__construct('', $options); + } +} diff --git a/vendor/symfony/console/Helper/TableStyle.php b/vendor/symfony/console/Helper/TableStyle.php new file mode 100644 index 00000000..2999c76f --- /dev/null +++ b/vendor/symfony/console/Helper/TableStyle.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\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Defines the styles for a Table. + * + * @author Fabien Potencier + * @author Саша Стаменковић + */ +class TableStyle +{ + private $paddingChar = ' '; + private $horizontalBorderChar = '-'; + private $verticalBorderChar = '|'; + private $crossingChar = '+'; + private $cellHeaderFormat = '%s'; + private $cellRowFormat = '%s'; + private $cellRowContentFormat = ' %s '; + private $borderFormat = '%s'; + private $padType = STR_PAD_RIGHT; + + /** + * Sets padding character, used for cell padding. + * + * @param string $paddingChar + * + * @return $this + */ + public function setPaddingChar($paddingChar) + { + if (!$paddingChar) { + throw new LogicException('The padding char must not be empty'); + } + + $this->paddingChar = $paddingChar; + + return $this; + } + + /** + * Gets padding character, used for cell padding. + * + * @return string + */ + public function getPaddingChar() + { + return $this->paddingChar; + } + + /** + * Sets horizontal border character. + * + * @param string $horizontalBorderChar + * + * @return $this + */ + public function setHorizontalBorderChar($horizontalBorderChar) + { + $this->horizontalBorderChar = $horizontalBorderChar; + + return $this; + } + + /** + * Gets horizontal border character. + * + * @return string + */ + public function getHorizontalBorderChar() + { + return $this->horizontalBorderChar; + } + + /** + * Sets vertical border character. + * + * @param string $verticalBorderChar + * + * @return $this + */ + public function setVerticalBorderChar($verticalBorderChar) + { + $this->verticalBorderChar = $verticalBorderChar; + + return $this; + } + + /** + * Gets vertical border character. + * + * @return string + */ + public function getVerticalBorderChar() + { + return $this->verticalBorderChar; + } + + /** + * Sets crossing character. + * + * @param string $crossingChar + * + * @return $this + */ + public function setCrossingChar($crossingChar) + { + $this->crossingChar = $crossingChar; + + return $this; + } + + /** + * Gets crossing character. + * + * @return string $crossingChar + */ + public function getCrossingChar() + { + return $this->crossingChar; + } + + /** + * Sets header cell format. + * + * @param string $cellHeaderFormat + * + * @return $this + */ + public function setCellHeaderFormat($cellHeaderFormat) + { + $this->cellHeaderFormat = $cellHeaderFormat; + + return $this; + } + + /** + * Gets header cell format. + * + * @return string + */ + public function getCellHeaderFormat() + { + return $this->cellHeaderFormat; + } + + /** + * Sets row cell format. + * + * @param string $cellRowFormat + * + * @return $this + */ + public function setCellRowFormat($cellRowFormat) + { + $this->cellRowFormat = $cellRowFormat; + + return $this; + } + + /** + * Gets row cell format. + * + * @return string + */ + public function getCellRowFormat() + { + return $this->cellRowFormat; + } + + /** + * Sets row cell content format. + * + * @param string $cellRowContentFormat + * + * @return $this + */ + public function setCellRowContentFormat($cellRowContentFormat) + { + $this->cellRowContentFormat = $cellRowContentFormat; + + return $this; + } + + /** + * Gets row cell content format. + * + * @return string + */ + public function getCellRowContentFormat() + { + return $this->cellRowContentFormat; + } + + /** + * Sets table border format. + * + * @param string $borderFormat + * + * @return $this + */ + public function setBorderFormat($borderFormat) + { + $this->borderFormat = $borderFormat; + + return $this; + } + + /** + * Gets table border format. + * + * @return string + */ + public function getBorderFormat() + { + return $this->borderFormat; + } + + /** + * Sets cell padding type. + * + * @param int $padType STR_PAD_* + * + * @return $this + */ + public function setPadType($padType) + { + if (!in_array($padType, array(STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH), true)) { + throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).'); + } + + $this->padType = $padType; + + return $this; + } + + /** + * Gets cell padding type. + * + * @return int + */ + public function getPadType() + { + return $this->padType; + } +} diff --git a/vendor/symfony/console/Input/ArgvInput.php b/vendor/symfony/console/Input/ArgvInput.php new file mode 100644 index 00000000..9264daab --- /dev/null +++ b/vendor/symfony/console/Input/ArgvInput.php @@ -0,0 +1,345 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * ArgvInput represents an input coming from the CLI arguments. + * + * Usage: + * + * $input = new ArgvInput(); + * + * By default, the `$_SERVER['argv']` array is used for the input values. + * + * This can be overridden by explicitly passing the input values in the constructor: + * + * $input = new ArgvInput($_SERVER['argv']); + * + * If you pass it yourself, don't forget that the first element of the array + * is the name of the running application. + * + * When passing an argument to the constructor, be sure that it respects + * the same rules as the argv one. It's almost always better to use the + * `StringInput` when you want to provide your own input. + * + * @author Fabien Potencier + * + * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html + * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 + */ +class ArgvInput extends Input +{ + private $tokens; + private $parsed; + + /** + * Constructor. + * + * @param array|null $argv An array of parameters from the CLI (in the argv format) + * @param InputDefinition|null $definition A InputDefinition instance + */ + public function __construct(array $argv = null, InputDefinition $definition = null) + { + if (null === $argv) { + $argv = $_SERVER['argv']; + } + + // strip the application name + array_shift($argv); + + $this->tokens = $argv; + + parent::__construct($definition); + } + + protected function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + /** + * {@inheritdoc} + */ + protected function parse() + { + $parseOptions = true; + $this->parsed = $this->tokens; + while (null !== $token = array_shift($this->parsed)) { + if ($parseOptions && '' == $token) { + $this->parseArgument($token); + } elseif ($parseOptions && '--' == $token) { + $parseOptions = false; + } elseif ($parseOptions && 0 === strpos($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { + $this->parseShortOption($token); + } else { + $this->parseArgument($token); + } + } + } + + /** + * Parses a short option. + * + * @param string $token The current token + */ + private function parseShortOption($token) + { + $name = substr($token, 1); + + if (strlen($name) > 1) { + if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { + // an option with a value (with no space) + $this->addShortOption($name[0], substr($name, 1)); + } else { + $this->parseShortOptionSet($name); + } + } else { + $this->addShortOption($name, null); + } + } + + /** + * Parses a short option set. + * + * @param string $name The current token + * + * @throws RuntimeException When option given doesn't exist + */ + private function parseShortOptionSet($name) + { + $len = strlen($name); + for ($i = 0; $i < $len; ++$i) { + if (!$this->definition->hasShortcut($name[$i])) { + throw new RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); + } + + $option = $this->definition->getOptionForShortcut($name[$i]); + if ($option->acceptValue()) { + $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); + + break; + } else { + $this->addLongOption($option->getName(), null); + } + } + } + + /** + * Parses a long option. + * + * @param string $token The current token + */ + private function parseLongOption($token) + { + $name = substr($token, 2); + + if (false !== $pos = strpos($name, '=')) { + if (0 === strlen($value = substr($name, $pos + 1))) { + // if no value after "=" then substr() returns "" since php7 only, false before + // see http://php.net/manual/fr/migration70.incompatible.php#119151 + if (\PHP_VERSION_ID < 70000 && false === $value) { + $value = ''; + } + array_unshift($this->parsed, $value); + } + $this->addLongOption(substr($name, 0, $pos), $value); + } else { + $this->addLongOption($name, null); + } + } + + /** + * Parses an argument. + * + * @param string $token The current token + * + * @throws RuntimeException When too many arguments are given + */ + private function parseArgument($token) + { + $c = count($this->arguments); + + // if input is expecting another argument, add it + if ($this->definition->hasArgument($c)) { + $arg = $this->definition->getArgument($c); + $this->arguments[$arg->getName()] = $arg->isArray() ? array($token) : $token; + + // if last argument isArray(), append token to last argument + } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { + $arg = $this->definition->getArgument($c - 1); + $this->arguments[$arg->getName()][] = $token; + + // unexpected argument + } else { + $all = $this->definition->getArguments(); + if (count($all)) { + throw new RuntimeException(sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)))); + } + + throw new RuntimeException(sprintf('No arguments expected, got "%s".', $token)); + } + } + + /** + * Adds a short option value. + * + * @param string $shortcut The short option key + * @param mixed $value The value for the option + * + * @throws RuntimeException When option given doesn't exist + */ + private function addShortOption($shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @param string $name The long option key + * @param mixed $value The value for the option + * + * @throws RuntimeException When option given doesn't exist + */ + private function addLongOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + if (null !== $value && !$option->acceptValue()) { + throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); + } + + if (in_array($value, array('', null), true) && $option->acceptValue() && count($this->parsed)) { + // if option accepts an optional or mandatory argument + // let's see if there is one provided + $next = array_shift($this->parsed); + if ((isset($next[0]) && '-' !== $next[0]) || in_array($next, array('', null), true)) { + $value = $next; + } else { + array_unshift($this->parsed, $next); + } + } + + if (null === $value) { + if ($option->isValueRequired()) { + throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray() && !$option->isValueOptional()) { + $value = true; + } + } + + if ($option->isArray()) { + $this->options[$name][] = $value; + } else { + $this->options[$name] = $value; + } + } + + /** + * {@inheritdoc} + */ + public function getFirstArgument() + { + foreach ($this->tokens as $token) { + if ($token && '-' === $token[0]) { + continue; + } + + return $token; + } + } + + /** + * {@inheritdoc} + */ + public function hasParameterOption($values, $onlyParams = false) + { + $values = (array) $values; + + foreach ($this->tokens as $token) { + if ($onlyParams && $token === '--') { + return false; + } + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value.'=')) { + return true; + } + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getParameterOption($values, $default = false, $onlyParams = false) + { + $values = (array) $values; + $tokens = $this->tokens; + + while (0 < count($tokens)) { + $token = array_shift($tokens); + if ($onlyParams && $token === '--') { + return false; + } + + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value.'=')) { + if (false !== $pos = strpos($token, '=')) { + return substr($token, $pos + 1); + } + + return array_shift($tokens); + } + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command. + * + * @return string + */ + public function __toString() + { + $tokens = array_map(function ($token) { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1].$this->escapeToken($match[2]); + } + + if ($token && $token[0] !== '-') { + return $this->escapeToken($token); + } + + return $token; + }, $this->tokens); + + return implode(' ', $tokens); + } +} diff --git a/vendor/symfony/console/Input/ArrayInput.php b/vendor/symfony/console/Input/ArrayInput.php new file mode 100644 index 00000000..434ec024 --- /dev/null +++ b/vendor/symfony/console/Input/ArrayInput.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\InvalidOptionException; + +/** + * ArrayInput represents an input provided as an array. + * + * Usage: + * + * $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar')); + * + * @author Fabien Potencier + */ +class ArrayInput extends Input +{ + private $parameters; + + /** + * Constructor. + * + * @param array $parameters An array of parameters + * @param InputDefinition|null $definition A InputDefinition instance + */ + public function __construct(array $parameters, InputDefinition $definition = null) + { + $this->parameters = $parameters; + + parent::__construct($definition); + } + + /** + * {@inheritdoc} + */ + public function getFirstArgument() + { + foreach ($this->parameters as $key => $value) { + if ($key && '-' === $key[0]) { + continue; + } + + return $value; + } + } + + /** + * {@inheritdoc} + */ + public function hasParameterOption($values, $onlyParams = false) + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if (!is_int($k)) { + $v = $k; + } + + if ($onlyParams && $v === '--') { + return false; + } + + if (in_array($v, $values)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getParameterOption($values, $default = false, $onlyParams = false) + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if ($onlyParams && ($k === '--' || (is_int($k) && $v === '--'))) { + return false; + } + + if (is_int($k)) { + if (in_array($v, $values)) { + return true; + } + } elseif (in_array($k, $values)) { + return $v; + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command. + * + * @return string + */ + public function __toString() + { + $params = array(); + foreach ($this->parameters as $param => $val) { + if ($param && '-' === $param[0]) { + $params[] = $param.('' != $val ? '='.$this->escapeToken($val) : ''); + } else { + $params[] = $this->escapeToken($val); + } + } + + return implode(' ', $params); + } + + /** + * {@inheritdoc} + */ + protected function parse() + { + foreach ($this->parameters as $key => $value) { + if ($key === '--') { + return; + } + if (0 === strpos($key, '--')) { + $this->addLongOption(substr($key, 2), $value); + } elseif ('-' === $key[0]) { + $this->addShortOption(substr($key, 1), $value); + } else { + $this->addArgument($key, $value); + } + } + } + + /** + * Adds a short option value. + * + * @param string $shortcut The short option key + * @param mixed $value The value for the option + * + * @throws InvalidOptionException When option given doesn't exist + */ + private function addShortOption($shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @param string $name The long option key + * @param mixed $value The value for the option + * + * @throws InvalidOptionException When option given doesn't exist + * @throws InvalidOptionException When a required value is missing + */ + private function addLongOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + if (null === $value) { + if ($option->isValueRequired()) { + throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isValueOptional()) { + $value = true; + } + } + + $this->options[$name] = $value; + } + + /** + * Adds an argument value. + * + * @param string $name The argument name + * @param mixed $value The value for the argument + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + private function addArgument($name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } +} diff --git a/vendor/symfony/console/Input/Input.php b/vendor/symfony/console/Input/Input.php new file mode 100644 index 00000000..244e7d4e --- /dev/null +++ b/vendor/symfony/console/Input/Input.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * Input is the base class for all concrete Input classes. + * + * Three concrete classes are provided by default: + * + * * `ArgvInput`: The input comes from the CLI arguments (argv) + * * `StringInput`: The input is provided as a string + * * `ArrayInput`: The input is provided as an array + * + * @author Fabien Potencier + */ +abstract class Input implements InputInterface, StreamableInputInterface +{ + /** + * @var InputDefinition + */ + protected $definition; + protected $stream; + protected $options = array(); + protected $arguments = array(); + protected $interactive = true; + + /** + * Constructor. + * + * @param InputDefinition|null $definition A InputDefinition instance + */ + public function __construct(InputDefinition $definition = null) + { + if (null === $definition) { + $this->definition = new InputDefinition(); + } else { + $this->bind($definition); + $this->validate(); + } + } + + /** + * {@inheritdoc} + */ + public function bind(InputDefinition $definition) + { + $this->arguments = array(); + $this->options = array(); + $this->definition = $definition; + + $this->parse(); + } + + /** + * Processes command line arguments. + */ + abstract protected function parse(); + + /** + * {@inheritdoc} + */ + public function validate() + { + $definition = $this->definition; + $givenArguments = $this->arguments; + + $missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) { + return !array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired(); + }); + + if (count($missingArguments) > 0) { + throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments))); + } + } + + /** + * {@inheritdoc} + */ + public function isInteractive() + { + return $this->interactive; + } + + /** + * {@inheritdoc} + */ + public function setInteractive($interactive) + { + $this->interactive = (bool) $interactive; + } + + /** + * {@inheritdoc} + */ + public function getArguments() + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * {@inheritdoc} + */ + public function getArgument($name) + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault(); + } + + /** + * {@inheritdoc} + */ + public function setArgument($name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function hasArgument($name) + { + return $this->definition->hasArgument($name); + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * {@inheritdoc} + */ + public function getOption($name) + { + if (!$this->definition->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); + } + + /** + * {@inheritdoc} + */ + public function setOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function hasOption($name) + { + return $this->definition->hasOption($name); + } + + /** + * Escapes a token through escapeshellarg if it contains unsafe chars. + * + * @param string $token + * + * @return string + */ + public function escapeToken($token) + { + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); + } + + /** + * {@inheritdoc} + */ + public function setStream($stream) + { + $this->stream = $stream; + } + + /** + * {@inheritdoc} + */ + public function getStream() + { + return $this->stream; + } +} diff --git a/vendor/symfony/console/Input/InputArgument.php b/vendor/symfony/console/Input/InputArgument.php new file mode 100644 index 00000000..048ee4ff --- /dev/null +++ b/vendor/symfony/console/Input/InputArgument.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\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a command line argument. + * + * @author Fabien Potencier + */ +class InputArgument +{ + const REQUIRED = 1; + const OPTIONAL = 2; + const IS_ARRAY = 4; + + private $name; + private $mode; + private $default; + private $description; + + /** + * Constructor. + * + * @param string $name The argument name + * @param int $mode The argument mode: self::REQUIRED or self::OPTIONAL + * @param string $description A description text + * @param mixed $default The default value (for self::OPTIONAL mode only) + * + * @throws InvalidArgumentException When argument mode is not valid + */ + public function __construct($name, $mode = null, $description = '', $default = null) + { + if (null === $mode) { + $mode = self::OPTIONAL; + } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { + throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->mode = $mode; + $this->description = $description; + + $this->setDefault($default); + } + + /** + * Returns the argument name. + * + * @return string The argument name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns true if the argument is required. + * + * @return bool true if parameter mode is self::REQUIRED, false otherwise + */ + public function isRequired() + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * Returns true if the argument can take multiple values. + * + * @return bool true if mode is self::IS_ARRAY, false otherwise + */ + public function isArray() + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * Sets the default value. + * + * @param mixed $default The default value + * + * @throws LogicException When incorrect default value is given + */ + public function setDefault($default = null) + { + if (self::REQUIRED === $this->mode && null !== $default) { + throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = array(); + } elseif (!is_array($default)) { + throw new LogicException('A default value for an array argument must be an array.'); + } + } + + $this->default = $default; + } + + /** + * Returns the default value. + * + * @return mixed The default value + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns the description text. + * + * @return string The description text + */ + public function getDescription() + { + return $this->description; + } +} diff --git a/vendor/symfony/console/Input/InputAwareInterface.php b/vendor/symfony/console/Input/InputAwareInterface.php new file mode 100644 index 00000000..d0f11e98 --- /dev/null +++ b/vendor/symfony/console/Input/InputAwareInterface.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\Console\Input; + +/** + * InputAwareInterface should be implemented by classes that depends on the + * Console Input. + * + * @author Wouter J + */ +interface InputAwareInterface +{ + /** + * Sets the Console Input. + * + * @param InputInterface + */ + public function setInput(InputInterface $input); +} diff --git a/vendor/symfony/console/Input/InputDefinition.php b/vendor/symfony/console/Input/InputDefinition.php new file mode 100644 index 00000000..85b778b2 --- /dev/null +++ b/vendor/symfony/console/Input/InputDefinition.php @@ -0,0 +1,414 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * A InputDefinition represents a set of valid command line arguments and options. + * + * Usage: + * + * $definition = new InputDefinition(array( + * new InputArgument('name', InputArgument::REQUIRED), + * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED), + * )); + * + * @author Fabien Potencier + */ +class InputDefinition +{ + private $arguments; + private $requiredCount; + private $hasAnArrayArgument = false; + private $hasOptional; + private $options; + private $shortcuts; + + /** + * Constructor. + * + * @param array $definition An array of InputArgument and InputOption instance + */ + public function __construct(array $definition = array()) + { + $this->setDefinition($definition); + } + + /** + * Sets the definition of the input. + * + * @param array $definition The definition array + */ + public function setDefinition(array $definition) + { + $arguments = array(); + $options = array(); + foreach ($definition as $item) { + if ($item instanceof InputOption) { + $options[] = $item; + } else { + $arguments[] = $item; + } + } + + $this->setArguments($arguments); + $this->setOptions($options); + } + + /** + * Sets the InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + */ + public function setArguments($arguments = array()) + { + $this->arguments = array(); + $this->requiredCount = 0; + $this->hasOptional = false; + $this->hasAnArrayArgument = false; + $this->addArguments($arguments); + } + + /** + * Adds an array of InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + */ + public function addArguments($arguments = array()) + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * Adds an InputArgument object. + * + * @param InputArgument $argument An InputArgument object + * + * @throws LogicException When incorrect argument is given + */ + public function addArgument(InputArgument $argument) + { + if (isset($this->arguments[$argument->getName()])) { + throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); + } + + if ($this->hasAnArrayArgument) { + throw new LogicException('Cannot add an argument after an array argument.'); + } + + if ($argument->isRequired() && $this->hasOptional) { + throw new LogicException('Cannot add a required argument after an optional one.'); + } + + if ($argument->isArray()) { + $this->hasAnArrayArgument = true; + } + + if ($argument->isRequired()) { + ++$this->requiredCount; + } else { + $this->hasOptional = true; + } + + $this->arguments[$argument->getName()] = $argument; + } + + /** + * Returns an InputArgument by name or by position. + * + * @param string|int $name The InputArgument name or position + * + * @return InputArgument An InputArgument object + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function getArgument($name) + { + if (!$this->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return $arguments[$name]; + } + + /** + * Returns true if an InputArgument object exists by name or position. + * + * @param string|int $name The InputArgument name or position + * + * @return bool true if the InputArgument object exists, false otherwise + */ + public function hasArgument($name) + { + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * Gets the array of InputArgument objects. + * + * @return InputArgument[] An array of InputArgument objects + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Returns the number of InputArguments. + * + * @return int The number of InputArguments + */ + public function getArgumentCount() + { + return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); + } + + /** + * Returns the number of required InputArguments. + * + * @return int The number of required InputArguments + */ + public function getArgumentRequiredCount() + { + return $this->requiredCount; + } + + /** + * Gets the default values. + * + * @return array An array of default values + */ + public function getArgumentDefaults() + { + $values = array(); + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * Sets the InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + */ + public function setOptions($options = array()) + { + $this->options = array(); + $this->shortcuts = array(); + $this->addOptions($options); + } + + /** + * Adds an array of InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + */ + public function addOptions($options = array()) + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * Adds an InputOption object. + * + * @param InputOption $option An InputOption object + * + * @throws LogicException When option given already exist + */ + public function addOption(InputOption $option) + { + if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { + throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { + throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); + } + } + } + + $this->options[$option->getName()] = $option; + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + $this->shortcuts[$shortcut] = $option->getName(); + } + } + } + + /** + * Returns an InputOption by name. + * + * @param string $name The InputOption name + * + * @return InputOption A InputOption object + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function getOption($name) + { + if (!$this->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + return $this->options[$name]; + } + + /** + * Returns true if an InputOption object exists by name. + * + * This method can't be used to check if the user included the option when + * executing the command (use getOption() instead). + * + * @param string $name The InputOption name + * + * @return bool true if the InputOption object exists, false otherwise + */ + public function hasOption($name) + { + return isset($this->options[$name]); + } + + /** + * Gets the array of InputOption objects. + * + * @return InputOption[] An array of InputOption objects + */ + public function getOptions() + { + return $this->options; + } + + /** + * Returns true if an InputOption object exists by shortcut. + * + * @param string $name The InputOption shortcut + * + * @return bool true if the InputOption object exists, false otherwise + */ + public function hasShortcut($name) + { + return isset($this->shortcuts[$name]); + } + + /** + * Gets an InputOption by shortcut. + * + * @param string $shortcut the Shortcut name + * + * @return InputOption An InputOption object + */ + public function getOptionForShortcut($shortcut) + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * Gets an array of default values. + * + * @return array An array of all default values + */ + public function getOptionDefaults() + { + $values = array(); + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * Returns the InputOption name given a shortcut. + * + * @param string $shortcut The shortcut + * + * @return string The InputOption name + * + * @throws InvalidArgumentException When option given does not exist + */ + private function shortcutToName($shortcut) + { + if (!isset($this->shortcuts[$shortcut])) { + throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + return $this->shortcuts[$shortcut]; + } + + /** + * Gets the synopsis. + * + * @param bool $short Whether to return the short version (with options folded) or not + * + * @return string The synopsis + */ + public function getSynopsis($short = false) + { + $elements = array(); + + if ($short && $this->getOptions()) { + $elements[] = '[options]'; + } elseif (!$short) { + foreach ($this->getOptions() as $option) { + $value = ''; + if ($option->acceptValue()) { + $value = sprintf( + ' %s%s%s', + $option->isValueOptional() ? '[' : '', + strtoupper($option->getName()), + $option->isValueOptional() ? ']' : '' + ); + } + + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); + } + } + + if (count($elements) && $this->getArguments()) { + $elements[] = '[--]'; + } + + foreach ($this->getArguments() as $argument) { + $element = '<'.$argument->getName().'>'; + if (!$argument->isRequired()) { + $element = '['.$element.']'; + } elseif ($argument->isArray()) { + $element = $element.' ('.$element.')'; + } + + if ($argument->isArray()) { + $element .= '...'; + } + + $elements[] = $element; + } + + return implode(' ', $elements); + } +} diff --git a/vendor/symfony/console/Input/InputInterface.php b/vendor/symfony/console/Input/InputInterface.php new file mode 100644 index 00000000..bc664664 --- /dev/null +++ b/vendor/symfony/console/Input/InputInterface.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\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * InputInterface is the interface implemented by all input classes. + * + * @author Fabien Potencier + */ +interface InputInterface +{ + /** + * Returns the first argument from the raw parameters (not parsed). + * + * @return string The value of the first argument or null otherwise + */ + public function getFirstArgument(); + + /** + * Returns true if the raw parameters (not parsed) contain a value. + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * + * @param string|array $values The values to look for in the raw parameters (can be an array) + * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal + * + * @return bool true if the value is contained in the raw parameters + */ + public function hasParameterOption($values, $onlyParams = false); + + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * @param mixed $default The default value to return if no result is found + * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal + * + * @return mixed The option value + */ + public function getParameterOption($values, $default = false, $onlyParams = false); + + /** + * Binds the current Input instance with the given arguments and options. + * + * @param InputDefinition $definition A InputDefinition instance + */ + public function bind(InputDefinition $definition); + + /** + * Validates the input. + * + * @throws RuntimeException When not enough arguments are given + */ + public function validate(); + + /** + * Returns all the given arguments merged with the default values. + * + * @return array + */ + public function getArguments(); + + /** + * Returns the argument value for a given argument name. + * + * @param string $name The argument name + * + * @return mixed The argument value + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function getArgument($name); + + /** + * Sets an argument value by name. + * + * @param string $name The argument name + * @param string $value The argument value + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function setArgument($name, $value); + + /** + * Returns true if an InputArgument object exists by name or position. + * + * @param string|int $name The InputArgument name or position + * + * @return bool true if the InputArgument object exists, false otherwise + */ + public function hasArgument($name); + + /** + * Returns all the given options merged with the default values. + * + * @return array + */ + public function getOptions(); + + /** + * Returns the option value for a given option name. + * + * @param string $name The option name + * + * @return mixed The option value + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function getOption($name); + + /** + * Sets an option value by name. + * + * @param string $name The option name + * @param string|bool $value The option value + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function setOption($name, $value); + + /** + * Returns true if an InputOption object exists by name. + * + * @param string $name The InputOption name + * + * @return bool true if the InputOption object exists, false otherwise + */ + public function hasOption($name); + + /** + * Is this input means interactive? + * + * @return bool + */ + public function isInteractive(); + + /** + * Sets the input interactivity. + * + * @param bool $interactive If the input should be interactive + */ + public function setInteractive($interactive); +} diff --git a/vendor/symfony/console/Input/InputOption.php b/vendor/symfony/console/Input/InputOption.php new file mode 100644 index 00000000..f08c5f26 --- /dev/null +++ b/vendor/symfony/console/Input/InputOption.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a command line option. + * + * @author Fabien Potencier + */ +class InputOption +{ + const VALUE_NONE = 1; + const VALUE_REQUIRED = 2; + const VALUE_OPTIONAL = 4; + const VALUE_IS_ARRAY = 8; + + private $name; + private $shortcut; + private $mode; + private $default; + private $description; + + /** + * Constructor. + * + * @param string $name The option name + * @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int $mode The option mode: One of the VALUE_* constants + * @param string $description A description text + * @param mixed $default The default value (must be null for self::VALUE_NONE) + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + */ + public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + if (0 === strpos($name, '--')) { + $name = substr($name, 2); + } + + if (empty($name)) { + throw new InvalidArgumentException('An option name cannot be empty.'); + } + + if (empty($shortcut)) { + $shortcut = null; + } + + if (null !== $shortcut) { + if (is_array($shortcut)) { + $shortcut = implode('|', $shortcut); + } + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts); + $shortcut = implode('|', $shortcuts); + + if (empty($shortcut)) { + throw new InvalidArgumentException('An option shortcut cannot be empty.'); + } + } + + if (null === $mode) { + $mode = self::VALUE_NONE; + } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { + throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->shortcut = $shortcut; + $this->mode = $mode; + $this->description = $description; + + if ($this->isArray() && !$this->acceptValue()) { + throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); + } + + $this->setDefault($default); + } + + /** + * Returns the option shortcut. + * + * @return string The shortcut + */ + public function getShortcut() + { + return $this->shortcut; + } + + /** + * Returns the option name. + * + * @return string The name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns true if the option accepts a value. + * + * @return bool true if value mode is not self::VALUE_NONE, false otherwise + */ + public function acceptValue() + { + return $this->isValueRequired() || $this->isValueOptional(); + } + + /** + * Returns true if the option requires a value. + * + * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise + */ + public function isValueRequired() + { + return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); + } + + /** + * Returns true if the option takes an optional value. + * + * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise + */ + public function isValueOptional() + { + return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); + } + + /** + * Returns true if the option can take multiple values. + * + * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise + */ + public function isArray() + { + return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); + } + + /** + * Sets the default value. + * + * @param mixed $default The default value + * + * @throws LogicException When incorrect default value is given + */ + public function setDefault($default = null) + { + if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { + throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = array(); + } elseif (!is_array($default)) { + throw new LogicException('A default value for an array option must be an array.'); + } + } + + $this->default = $this->acceptValue() ? $default : false; + } + + /** + * Returns the default value. + * + * @return mixed The default value + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns the description text. + * + * @return string The description text + */ + public function getDescription() + { + return $this->description; + } + + /** + * Checks whether the given option equals this one. + * + * @param InputOption $option option to compare + * + * @return bool + */ + public function equals(InputOption $option) + { + return $option->getName() === $this->getName() + && $option->getShortcut() === $this->getShortcut() + && $option->getDefault() === $this->getDefault() + && $option->isArray() === $this->isArray() + && $option->isValueRequired() === $this->isValueRequired() + && $option->isValueOptional() === $this->isValueOptional() + ; + } +} diff --git a/vendor/symfony/console/Input/StreamableInputInterface.php b/vendor/symfony/console/Input/StreamableInputInterface.php new file mode 100644 index 00000000..d7e462f2 --- /dev/null +++ b/vendor/symfony/console/Input/StreamableInputInterface.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\Console\Input; + +/** + * StreamableInputInterface is the interface implemented by all input classes + * that have an input stream. + * + * @author Robin Chalas + */ +interface StreamableInputInterface extends InputInterface +{ + /** + * Sets the input stream to read from when interacting with the user. + * + * This is mainly useful for testing purpose. + * + * @param resource $stream The input stream + */ + public function setStream($stream); + + /** + * Returns the input stream. + * + * @return resource|null + */ + public function getStream(); +} diff --git a/vendor/symfony/console/Input/StringInput.php b/vendor/symfony/console/Input/StringInput.php new file mode 100644 index 00000000..9ce02174 --- /dev/null +++ b/vendor/symfony/console/Input/StringInput.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\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * StringInput represents an input provided as a string. + * + * Usage: + * + * $input = new StringInput('foo --bar="foobar"'); + * + * @author Fabien Potencier + */ +class StringInput extends ArgvInput +{ + const REGEX_STRING = '([^\s]+?)(?:\s|(?setTokens($this->tokenize($input)); + } + + /** + * Tokenizes a string. + * + * @param string $input The input to tokenize + * + * @return array An array of tokens + * + * @throws InvalidArgumentException When unable to parse input (should never happen) + */ + private function tokenize($input) + { + $tokens = array(); + $length = strlen($input); + $cursor = 0; + while ($cursor < $length) { + if (preg_match('/\s+/A', $input, $match, null, $cursor)) { + } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) { + $tokens[] = $match[1].$match[2].stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, strlen($match[3]) - 2))); + } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) { + $tokens[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2)); + } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) { + $tokens[] = stripcslashes($match[1]); + } else { + // should never happen + throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10))); + } + + $cursor += strlen($match[0]); + } + + return $tokens; + } +} diff --git a/vendor/symfony/console/LICENSE b/vendor/symfony/console/LICENSE new file mode 100644 index 00000000..17d16a13 --- /dev/null +++ b/vendor/symfony/console/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/vendor/symfony/console/Logger/ConsoleLogger.php b/vendor/symfony/console/Logger/ConsoleLogger.php new file mode 100644 index 00000000..208575ce --- /dev/null +++ b/vendor/symfony/console/Logger/ConsoleLogger.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\Console\Logger; + +use Psr\Log\AbstractLogger; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; + +/** + * PSR-3 compliant console logger. + * + * @author Kévin Dunglas + * + * @see http://www.php-fig.org/psr/psr-3/ + */ +class ConsoleLogger extends AbstractLogger +{ + const INFO = 'info'; + const ERROR = 'error'; + + /** + * @var OutputInterface + */ + private $output; + /** + * @var array + */ + private $verbosityLevelMap = array( + LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, + LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, + LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, + LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE, + LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, + LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG, + ); + /** + * @var array + */ + private $formatLevelMap = array( + LogLevel::EMERGENCY => self::ERROR, + LogLevel::ALERT => self::ERROR, + LogLevel::CRITICAL => self::ERROR, + LogLevel::ERROR => self::ERROR, + LogLevel::WARNING => self::INFO, + LogLevel::NOTICE => self::INFO, + LogLevel::INFO => self::INFO, + LogLevel::DEBUG => self::INFO, + ); + private $errored = false; + + /** + * @param OutputInterface $output + * @param array $verbosityLevelMap + * @param array $formatLevelMap + */ + public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array()) + { + $this->output = $output; + $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; + $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; + } + + /** + * {@inheritdoc} + */ + public function log($level, $message, array $context = array()) + { + if (!isset($this->verbosityLevelMap[$level])) { + throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); + } + + $output = $this->output; + + // Write to the error output if necessary and available + if ($this->formatLevelMap[$level] === self::ERROR) { + if ($this->output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + $this->errored = true; + } + + // the if condition check isn't necessary -- it's the same one that $output will do internally anyway. + // We only do it for efficiency here as the message formatting is relatively expensive. + if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { + $output->writeln(sprintf('<%1$s>[%2$s] %3$s', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]); + } + } + + /** + * Returns true when any messages have been logged at error levels. + */ + public function hasErrored() + { + return $this->errored; + } + + /** + * Interpolates context values into the message placeholders. + * + * @author PHP Framework Interoperability Group + * + * @param string $message + * @param array $context + * + * @return string + */ + private function interpolate($message, array $context) + { + // build a replacement array with braces around the context keys + $replace = array(); + foreach ($context as $key => $val) { + if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { + $replace[sprintf('{%s}', $key)] = $val; + } + } + + // interpolate replacement values into the message and return + return strtr($message, $replace); + } +} diff --git a/vendor/symfony/console/Output/BufferedOutput.php b/vendor/symfony/console/Output/BufferedOutput.php new file mode 100644 index 00000000..205b02f5 --- /dev/null +++ b/vendor/symfony/console/Output/BufferedOutput.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\Console\Output; + +/** + * @author Jean-François Simon + */ +class BufferedOutput extends Output +{ + /** + * @var string + */ + private $buffer = ''; + + /** + * Empties buffer and returns its content. + * + * @return string + */ + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + + return $content; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($message, $newline) + { + $this->buffer .= $message; + + if ($newline) { + $this->buffer .= PHP_EOL; + } + } +} diff --git a/vendor/symfony/console/Output/ConsoleOutput.php b/vendor/symfony/console/Output/ConsoleOutput.php new file mode 100644 index 00000000..007f3f01 --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleOutput.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\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR. + * + * This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR. + * + * $output = new ConsoleOutput(); + * + * This is equivalent to: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * $stdErr = new StreamOutput(fopen('php://stderr', 'w')); + * + * @author Fabien Potencier + */ +class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface +{ + /** + * @var StreamOutput + */ + private $stderr; + + /** + * Constructor. + * + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + */ + public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) + { + parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); + + $actualDecorated = $this->isDecorated(); + $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); + + if (null === $decorated) { + $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); + } + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + parent::setDecorated($decorated); + $this->stderr->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->stderr->setFormatter($formatter); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + parent::setVerbosity($level); + $this->stderr->setVerbosity($level); + } + + /** + * {@inheritdoc} + */ + public function getErrorOutput() + { + return $this->stderr; + } + + /** + * {@inheritdoc} + */ + public function setErrorOutput(OutputInterface $error) + { + $this->stderr = $error; + } + + /** + * Returns true if current environment supports writing console output to + * STDOUT. + * + * @return bool + */ + protected function hasStdoutSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * Returns true if current environment supports writing console output to + * STDERR. + * + * @return bool + */ + protected function hasStderrSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * Checks if current executing environment is IBM iSeries (OS400), which + * doesn't properly convert character-encodings between ASCII to EBCDIC. + * + * @return bool + */ + private function isRunningOS400() + { + $checks = array( + function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + PHP_OS, + ); + + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * @return resource + */ + private function openOutputStream() + { + if (!$this->hasStdoutSupport()) { + return fopen('php://output', 'w'); + } + + return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); + } + + /** + * @return resource + */ + private function openErrorStream() + { + return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); + } +} diff --git a/vendor/symfony/console/Output/ConsoleOutputInterface.php b/vendor/symfony/console/Output/ConsoleOutputInterface.php new file mode 100644 index 00000000..5eb4fc7a --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleOutputInterface.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\Console\Output; + +/** + * ConsoleOutputInterface is the interface implemented by ConsoleOutput class. + * This adds information about stderr output stream. + * + * @author Dariusz Górecki + */ +interface ConsoleOutputInterface extends OutputInterface +{ + /** + * Gets the OutputInterface for errors. + * + * @return OutputInterface + */ + public function getErrorOutput(); + + /** + * Sets the OutputInterface used for errors. + * + * @param OutputInterface $error + */ + public function setErrorOutput(OutputInterface $error); +} diff --git a/vendor/symfony/console/Output/NullOutput.php b/vendor/symfony/console/Output/NullOutput.php new file mode 100644 index 00000000..218f285b --- /dev/null +++ b/vendor/symfony/console/Output/NullOutput.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * NullOutput suppresses all output. + * + * $output = new NullOutput(); + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class NullOutput implements OutputInterface +{ + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + // to comply with the interface we must return a OutputFormatterInterface + return new OutputFormatter(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return self::VERBOSITY_QUIET; + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $options = self::OUTPUT_NORMAL) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL) + { + // do nothing + } +} diff --git a/vendor/symfony/console/Output/Output.php b/vendor/symfony/console/Output/Output.php new file mode 100644 index 00000000..c12015cc --- /dev/null +++ b/vendor/symfony/console/Output/Output.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * Base class for output classes. + * + * There are five levels of verbosity: + * + * * normal: no option passed (normal output) + * * verbose: -v (more output) + * * very verbose: -vv (highly extended output) + * * debug: -vvv (all debug output) + * * quiet: -q (no output) + * + * @author Fabien Potencier + */ +abstract class Output implements OutputInterface +{ + private $verbosity; + private $formatter; + + /** + * Constructor. + * + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool $decorated Whether to decorate messages + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + */ + public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = false, OutputFormatterInterface $formatter = null) + { + $this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity; + $this->formatter = $formatter ?: new OutputFormatter(); + $this->formatter->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + $this->formatter = $formatter; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->formatter; + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + $this->formatter->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->formatter->isDecorated(); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->verbosity = (int) $level; + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $options = self::OUTPUT_NORMAL) + { + $this->write($messages, true, $options); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL) + { + $messages = (array) $messages; + + $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN; + $type = $types & $options ?: self::OUTPUT_NORMAL; + + $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG; + $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL; + + if ($verbosity > $this->getVerbosity()) { + return; + } + + foreach ($messages as $message) { + switch ($type) { + case OutputInterface::OUTPUT_NORMAL: + $message = $this->formatter->format($message); + break; + case OutputInterface::OUTPUT_RAW: + break; + case OutputInterface::OUTPUT_PLAIN: + $message = strip_tags($this->formatter->format($message)); + break; + } + + $this->doWrite($message, $newline); + } + } + + /** + * Writes a message to the output. + * + * @param string $message A message to write to the output + * @param bool $newline Whether to add a newline or not + */ + abstract protected function doWrite($message, $newline); +} diff --git a/vendor/symfony/console/Output/OutputInterface.php b/vendor/symfony/console/Output/OutputInterface.php new file mode 100644 index 00000000..a291ca7d --- /dev/null +++ b/vendor/symfony/console/Output/OutputInterface.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * OutputInterface is the interface implemented by all Output classes. + * + * @author Fabien Potencier + */ +interface OutputInterface +{ + const VERBOSITY_QUIET = 16; + const VERBOSITY_NORMAL = 32; + const VERBOSITY_VERBOSE = 64; + const VERBOSITY_VERY_VERBOSE = 128; + const VERBOSITY_DEBUG = 256; + + const OUTPUT_NORMAL = 1; + const OUTPUT_RAW = 2; + const OUTPUT_PLAIN = 4; + + /** + * Writes a message to the output. + * + * @param string|array $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline + * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL + */ + public function write($messages, $newline = false, $options = 0); + + /** + * Writes a message to the output and adds a newline at the end. + * + * @param string|array $messages The message as an array of lines of a single string + * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL + */ + public function writeln($messages, $options = 0); + + /** + * Sets the verbosity of the output. + * + * @param int $level The level of verbosity (one of the VERBOSITY constants) + */ + public function setVerbosity($level); + + /** + * Gets the current verbosity of the output. + * + * @return int The current level of verbosity (one of the VERBOSITY constants) + */ + public function getVerbosity(); + + /** + * Returns whether verbosity is quiet (-q). + * + * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise + */ + public function isQuiet(); + + /** + * Returns whether verbosity is verbose (-v). + * + * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise + */ + public function isVerbose(); + + /** + * Returns whether verbosity is very verbose (-vv). + * + * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise + */ + public function isVeryVerbose(); + + /** + * Returns whether verbosity is debug (-vvv). + * + * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise + */ + public function isDebug(); + + /** + * Sets the decorated flag. + * + * @param bool $decorated Whether to decorate the messages + */ + public function setDecorated($decorated); + + /** + * Gets the decorated flag. + * + * @return bool true if the output will decorate messages, false otherwise + */ + public function isDecorated(); + + /** + * Sets output formatter. + * + * @param OutputFormatterInterface $formatter + */ + public function setFormatter(OutputFormatterInterface $formatter); + + /** + * Returns current output formatter instance. + * + * @return OutputFormatterInterface + */ + public function getFormatter(); +} diff --git a/vendor/symfony/console/Output/StreamOutput.php b/vendor/symfony/console/Output/StreamOutput.php new file mode 100644 index 00000000..22b29aa1 --- /dev/null +++ b/vendor/symfony/console/Output/StreamOutput.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\Console\Output; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * StreamOutput writes the output to a given stream. + * + * Usage: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * + * As `StreamOutput` can use any stream, you can also use a file: + * + * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); + * + * @author Fabien Potencier + */ +class StreamOutput extends Output +{ + private $stream; + + /** + * Constructor. + * + * @param resource $stream A stream resource + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + * + * @throws InvalidArgumentException When first argument is not a real stream + */ + public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) + { + if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) { + throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); + } + + $this->stream = $stream; + + if (null === $decorated) { + $decorated = $this->hasColorSupport(); + } + + parent::__construct($verbosity, $decorated, $formatter); + } + + /** + * Gets the stream attached to this StreamOutput instance. + * + * @return resource A stream resource + */ + public function getStream() + { + return $this->stream; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($message, $newline) + { + if (false === @fwrite($this->stream, $message) || ($newline && (false === @fwrite($this->stream, PHP_EOL)))) { + // should never happen + throw new RuntimeException('Unable to write output.'); + } + + fflush($this->stream); + } + + /** + * Returns true if the stream supports colorization. + * + * Colorization is disabled if not supported by the stream: + * + * - Windows != 10.0.10586 without Ansicon, ConEmu or Mintty + * - non tty consoles + * + * @return bool true if the stream supports colorization, false otherwise + */ + protected function hasColorSupport() + { + if (DIRECTORY_SEPARATOR === '\\') { + return + '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + return function_exists('posix_isatty') && @posix_isatty($this->stream); + } +} diff --git a/vendor/symfony/console/Question/ChoiceQuestion.php b/vendor/symfony/console/Question/ChoiceQuestion.php new file mode 100644 index 00000000..5815e2b0 --- /dev/null +++ b/vendor/symfony/console/Question/ChoiceQuestion.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * Represents a choice question. + * + * @author Fabien Potencier + */ +class ChoiceQuestion extends Question +{ + private $choices; + private $multiselect = false; + private $prompt = ' > '; + private $errorMessage = 'Value "%s" is invalid'; + + /** + * Constructor. + * + * @param string $question The question to ask to the user + * @param array $choices The list of available choices + * @param mixed $default The default answer to return + */ + public function __construct($question, array $choices, $default = null) + { + if (!$choices) { + throw new \LogicException('Choice question must have at least 1 choice available.'); + } + + parent::__construct($question, $default); + + $this->choices = $choices; + $this->setValidator($this->getDefaultValidator()); + $this->setAutocompleterValues($choices); + } + + /** + * Returns available choices. + * + * @return array + */ + public function getChoices() + { + return $this->choices; + } + + /** + * Sets multiselect option. + * + * When multiselect is set to true, multiple choices can be answered. + * + * @param bool $multiselect + * + * @return $this + */ + public function setMultiselect($multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * Returns whether the choices are multiselect. + * + * @return bool + */ + public function isMultiselect() + { + return $this->multiselect; + } + + /** + * Gets the prompt for choices. + * + * @return string + */ + public function getPrompt() + { + return $this->prompt; + } + + /** + * Sets the prompt for choices. + * + * @param string $prompt + * + * @return $this + */ + public function setPrompt($prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * Sets the error message for invalid values. + * + * The error message has a string placeholder (%s) for the invalid value. + * + * @param string $errorMessage + * + * @return $this + */ + public function setErrorMessage($errorMessage) + { + $this->errorMessage = $errorMessage; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * Returns the default answer validator. + * + * @return callable + */ + private function getDefaultValidator() + { + $choices = $this->choices; + $errorMessage = $this->errorMessage; + $multiselect = $this->multiselect; + $isAssoc = $this->isAssoc($choices); + + return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { + // Collapse all spaces. + $selectedChoices = str_replace(' ', '', $selected); + + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selectedChoices, $matches)) { + throw new InvalidArgumentException(sprintf($errorMessage, $selected)); + } + $selectedChoices = explode(',', $selectedChoices); + } else { + $selectedChoices = array($selected); + } + + $multiselectChoices = array(); + foreach ($selectedChoices as $value) { + $results = array(); + foreach ($choices as $key => $choice) { + if ($choice === $value) { + $results[] = $key; + } + } + + if (count($results) > 1) { + throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results))); + } + + $result = array_search($value, $choices); + + if (!$isAssoc) { + if (false !== $result) { + $result = $choices[$result]; + } elseif (isset($choices[$value])) { + $result = $choices[$value]; + } + } elseif (false === $result && isset($choices[$value])) { + $result = $value; + } + + if (false === $result) { + throw new InvalidArgumentException(sprintf($errorMessage, $value)); + } + + $multiselectChoices[] = (string) $result; + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/vendor/symfony/console/Question/ConfirmationQuestion.php b/vendor/symfony/console/Question/ConfirmationQuestion.php new file mode 100644 index 00000000..29d98879 --- /dev/null +++ b/vendor/symfony/console/Question/ConfirmationQuestion.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +/** + * Represents a yes/no question. + * + * @author Fabien Potencier + */ +class ConfirmationQuestion extends Question +{ + private $trueAnswerRegex; + + /** + * Constructor. + * + * @param string $question The question to ask to the user + * @param bool $default The default answer to return, true or false + * @param string $trueAnswerRegex A regex to match the "yes" answer + */ + public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i') + { + parent::__construct($question, (bool) $default); + + $this->trueAnswerRegex = $trueAnswerRegex; + $this->setNormalizer($this->getDefaultNormalizer()); + } + + /** + * Returns the default answer normalizer. + * + * @return callable + */ + private function getDefaultNormalizer() + { + $default = $this->getDefault(); + $regex = $this->trueAnswerRegex; + + return function ($answer) use ($default, $regex) { + if (is_bool($answer)) { + return $answer; + } + + $answerIsTrue = (bool) preg_match($regex, $answer); + if (false === $default) { + return $answer && $answerIsTrue; + } + + return !$answer || $answerIsTrue; + }; + } +} diff --git a/vendor/symfony/console/Question/Question.php b/vendor/symfony/console/Question/Question.php new file mode 100644 index 00000000..6425cc54 --- /dev/null +++ b/vendor/symfony/console/Question/Question.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\Console\Question; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a Question. + * + * @author Fabien Potencier + */ +class Question +{ + private $question; + private $attempts; + private $hidden = false; + private $hiddenFallback = true; + private $autocompleterValues; + private $validator; + private $default; + private $normalizer; + + /** + * Constructor. + * + * @param string $question The question to ask to the user + * @param mixed $default The default answer to return if the user enters nothing + */ + public function __construct($question, $default = null) + { + $this->question = $question; + $this->default = $default; + } + + /** + * Returns the question. + * + * @return string + */ + public function getQuestion() + { + return $this->question; + } + + /** + * Returns the default answer. + * + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns whether the user response must be hidden. + * + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * Sets whether the user response must be hidden or not. + * + * @param bool $hidden + * + * @return $this + * + * @throws LogicException In case the autocompleter is also used + */ + public function setHidden($hidden) + { + if ($this->autocompleterValues) { + throw new LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->hidden = (bool) $hidden; + + return $this; + } + + /** + * In case the response can not be hidden, whether to fallback on non-hidden question or not. + * + * @return bool + */ + public function isHiddenFallback() + { + return $this->hiddenFallback; + } + + /** + * Sets whether to fallback on non-hidden question if the response can not be hidden. + * + * @param bool $fallback + * + * @return $this + */ + public function setHiddenFallback($fallback) + { + $this->hiddenFallback = (bool) $fallback; + + return $this; + } + + /** + * Gets values for the autocompleter. + * + * @return null|array|\Traversable + */ + public function getAutocompleterValues() + { + return $this->autocompleterValues; + } + + /** + * Sets values for the autocompleter. + * + * @param null|array|\Traversable $values + * + * @return $this + * + * @throws InvalidArgumentException + * @throws LogicException + */ + public function setAutocompleterValues($values) + { + if (is_array($values)) { + $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); + } + + if (null !== $values && !is_array($values)) { + if (!$values instanceof \Traversable || !$values instanceof \Countable) { + throw new InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.'); + } + } + + if ($this->hidden) { + throw new LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->autocompleterValues = $values; + + return $this; + } + + /** + * Sets a validator for the question. + * + * @param null|callable $validator + * + * @return $this + */ + public function setValidator(callable $validator = null) + { + $this->validator = $validator; + + return $this; + } + + /** + * Gets the validator for the question. + * + * @return null|callable + */ + public function getValidator() + { + return $this->validator; + } + + /** + * Sets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + * + * @param null|int $attempts + * + * @return $this + * + * @throws InvalidArgumentException In case the number of attempts is invalid. + */ + public function setMaxAttempts($attempts) + { + if (null !== $attempts && $attempts < 1) { + throw new InvalidArgumentException('Maximum number of attempts must be a positive value.'); + } + + $this->attempts = $attempts; + + return $this; + } + + /** + * Gets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + * + * @return null|int + */ + public function getMaxAttempts() + { + return $this->attempts; + } + + /** + * Sets a normalizer for the response. + * + * The normalizer can be a callable (a string), a closure or a class implementing __invoke. + * + * @param callable $normalizer + * + * @return $this + */ + public function setNormalizer(callable $normalizer) + { + $this->normalizer = $normalizer; + + return $this; + } + + /** + * Gets the normalizer for the response. + * + * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. + * + * @return callable + */ + public function getNormalizer() + { + return $this->normalizer; + } + + protected function isAssoc($array) + { + return (bool) count(array_filter(array_keys($array), 'is_string')); + } +} diff --git a/vendor/symfony/console/README.md b/vendor/symfony/console/README.md new file mode 100644 index 00000000..664a37c0 --- /dev/null +++ b/vendor/symfony/console/README.md @@ -0,0 +1,20 @@ +Console Component +================= + +The Console component eases the creation of beautiful and testable command line +interfaces. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/console/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) + +Credits +------- + +`Resources/bin/hiddeninput.exe` is a third party binary provided within this +component. Find sources and license at https://github.com/Seldaek/hidden-input. diff --git a/vendor/symfony/console/Resources/bin/hiddeninput.exe b/vendor/symfony/console/Resources/bin/hiddeninput.exe new file mode 100644 index 00000000..c8cf65e8 Binary files /dev/null and b/vendor/symfony/console/Resources/bin/hiddeninput.exe differ diff --git a/vendor/symfony/console/Style/OutputStyle.php b/vendor/symfony/console/Style/OutputStyle.php new file mode 100644 index 00000000..1274a98d --- /dev/null +++ b/vendor/symfony/console/Style/OutputStyle.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; + +/** + * Decorates output to add console style guide helpers. + * + * @author Kevin Bond + */ +abstract class OutputStyle implements OutputInterface, StyleInterface +{ + private $output; + + /** + * @param OutputInterface $output + */ + public function __construct(OutputInterface $output) + { + $this->output = $output; + } + + /** + * {@inheritdoc} + */ + public function newLine($count = 1) + { + $this->output->write(str_repeat(PHP_EOL, $count)); + } + + /** + * @param int $max + * + * @return ProgressBar + */ + public function createProgressBar($max = 0) + { + return new ProgressBar($this->output, $max); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + $this->output->write($messages, $newline, $type); + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + $this->output->writeln($messages, $type); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->output->setVerbosity($level); + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->output->getVerbosity(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + $this->output->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->output->isDecorated(); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + $this->output->setFormatter($formatter); + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->output->getFormatter(); + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return $this->output->isQuiet(); + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return $this->output->isVerbose(); + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return $this->output->isVeryVerbose(); + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return $this->output->isDebug(); + } + + protected function getErrorOutput() + { + if (!$this->output instanceof ConsoleOutputInterface) { + return $this->output; + } + + return $this->output->getErrorOutput(); + } +} diff --git a/vendor/symfony/console/Style/StyleInterface.php b/vendor/symfony/console/Style/StyleInterface.php new file mode 100644 index 00000000..2448547f --- /dev/null +++ b/vendor/symfony/console/Style/StyleInterface.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\Console\Style; + +/** + * Output style helpers. + * + * @author Kevin Bond + */ +interface StyleInterface +{ + /** + * Formats a command title. + * + * @param string $message + */ + public function title($message); + + /** + * Formats a section title. + * + * @param string $message + */ + public function section($message); + + /** + * Formats a list. + * + * @param array $elements + */ + public function listing(array $elements); + + /** + * Formats informational text. + * + * @param string|array $message + */ + public function text($message); + + /** + * Formats a success result bar. + * + * @param string|array $message + */ + public function success($message); + + /** + * Formats an error result bar. + * + * @param string|array $message + */ + public function error($message); + + /** + * Formats an warning result bar. + * + * @param string|array $message + */ + public function warning($message); + + /** + * Formats a note admonition. + * + * @param string|array $message + */ + public function note($message); + + /** + * Formats a caution admonition. + * + * @param string|array $message + */ + public function caution($message); + + /** + * Formats a table. + * + * @param array $headers + * @param array $rows + */ + public function table(array $headers, array $rows); + + /** + * Asks a question. + * + * @param string $question + * @param string|null $default + * @param callable|null $validator + * + * @return string + */ + public function ask($question, $default = null, $validator = null); + + /** + * Asks a question with the user input hidden. + * + * @param string $question + * @param callable|null $validator + * + * @return string + */ + public function askHidden($question, $validator = null); + + /** + * Asks for confirmation. + * + * @param string $question + * @param bool $default + * + * @return bool + */ + public function confirm($question, $default = true); + + /** + * Asks a choice question. + * + * @param string $question + * @param array $choices + * @param string|int|null $default + * + * @return string + */ + public function choice($question, array $choices, $default = null); + + /** + * Add newline(s). + * + * @param int $count The number of newlines + */ + public function newLine($count = 1); + + /** + * Starts the progress output. + * + * @param int $max Maximum steps (0 if unknown) + */ + public function progressStart($max = 0); + + /** + * Advances the progress output X steps. + * + * @param int $step Number of steps to advance + */ + public function progressAdvance($step = 1); + + /** + * Finishes the progress output. + */ + public function progressFinish(); +} diff --git a/vendor/symfony/console/Style/SymfonyStyle.php b/vendor/symfony/console/Style/SymfonyStyle.php new file mode 100644 index 00000000..a8679617 --- /dev/null +++ b/vendor/symfony/console/Style/SymfonyStyle.php @@ -0,0 +1,435 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Helper\SymfonyQuestionHelper; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Terminal; + +/** + * Output decorator helpers for the Symfony Style Guide. + * + * @author Kevin Bond + */ +class SymfonyStyle extends OutputStyle +{ + const MAX_LINE_LENGTH = 120; + + private $input; + private $questionHelper; + private $progressBar; + private $lineLength; + private $bufferedOutput; + + /** + * @param InputInterface $input + * @param OutputInterface $output + */ + public function __construct(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter()); + // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. + $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; + $this->lineLength = min($width - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); + + parent::__construct($output); + } + + /** + * Formats a message as a block of text. + * + * @param string|array $messages The message to write in the block + * @param string|null $type The block type (added in [] on first line) + * @param string|null $style The style to apply to the whole block + * @param string $prefix The prefix for the block + * @param bool $padding Whether to add vertical padding + * @param bool $escape Whether to escape the message + */ + public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = true) + { + $messages = is_array($messages) ? array_values($messages) : array($messages); + + $this->autoPrependBlock(); + $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape)); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function title($message) + { + $this->autoPrependBlock(); + $this->writeln(array( + sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('%s', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), + )); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function section($message) + { + $this->autoPrependBlock(); + $this->writeln(array( + sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('%s', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), + )); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function listing(array $elements) + { + $this->autoPrependText(); + $elements = array_map(function ($element) { + return sprintf(' * %s', $element); + }, $elements); + + $this->writeln($elements); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function text($message) + { + $this->autoPrependText(); + + $messages = is_array($message) ? array_values($message) : array($message); + foreach ($messages as $message) { + $this->writeln(sprintf(' %s', $message)); + } + } + + /** + * Formats a command comment. + * + * @param string|array $message + */ + public function comment($message) + { + $this->block($message, null, null, ' // ', false, false); + } + + /** + * {@inheritdoc} + */ + public function success($message) + { + $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function error($message) + { + $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function warning($message) + { + $this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function note($message) + { + $this->block($message, 'NOTE', 'fg=yellow', ' ! '); + } + + /** + * {@inheritdoc} + */ + public function caution($message) + { + $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); + } + + /** + * {@inheritdoc} + */ + public function table(array $headers, array $rows) + { + $style = clone Table::getStyleDefinition('symfony-style-guide'); + $style->setCellHeaderFormat('%s'); + + $table = new Table($this); + $table->setHeaders($headers); + $table->setRows($rows); + $table->setStyle($style); + + $table->render(); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function ask($question, $default = null, $validator = null) + { + $question = new Question($question, $default); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + /** + * {@inheritdoc} + */ + public function askHidden($question, $validator = null) + { + $question = new Question($question); + + $question->setHidden(true); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + /** + * {@inheritdoc} + */ + public function confirm($question, $default = true) + { + return $this->askQuestion(new ConfirmationQuestion($question, $default)); + } + + /** + * {@inheritdoc} + */ + public function choice($question, array $choices, $default = null) + { + if (null !== $default) { + $values = array_flip($choices); + $default = $values[$default]; + } + + return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); + } + + /** + * {@inheritdoc} + */ + public function progressStart($max = 0) + { + $this->progressBar = $this->createProgressBar($max); + $this->progressBar->start(); + } + + /** + * {@inheritdoc} + */ + public function progressAdvance($step = 1) + { + $this->getProgressBar()->advance($step); + } + + /** + * {@inheritdoc} + */ + public function progressFinish() + { + $this->getProgressBar()->finish(); + $this->newLine(2); + $this->progressBar = null; + } + + /** + * {@inheritdoc} + */ + public function createProgressBar($max = 0) + { + $progressBar = parent::createProgressBar($max); + + if ('\\' !== DIRECTORY_SEPARATOR) { + $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 + $progressBar->setProgressCharacter(''); + $progressBar->setBarCharacter('▓'); // dark shade character \u2593 + } + + return $progressBar; + } + + /** + * @param Question $question + * + * @return string + */ + public function askQuestion(Question $question) + { + if ($this->input->isInteractive()) { + $this->autoPrependBlock(); + } + + if (!$this->questionHelper) { + $this->questionHelper = new SymfonyQuestionHelper(); + } + + $answer = $this->questionHelper->ask($this->input, $this, $question); + + if ($this->input->isInteractive()) { + $this->newLine(); + $this->bufferedOutput->write("\n"); + } + + return $answer; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + parent::writeln($messages, $type); + $this->bufferedOutput->writeln($this->reduceBuffer($messages), $type); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + parent::write($messages, $newline, $type); + $this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type); + } + + /** + * {@inheritdoc} + */ + public function newLine($count = 1) + { + parent::newLine($count); + $this->bufferedOutput->write(str_repeat("\n", $count)); + } + + /** + * Returns a new instance which makes use of stderr if available. + * + * @return self + */ + public function getErrorStyle() + { + return new self($this->input, $this->getErrorOutput()); + } + + /** + * @return ProgressBar + */ + private function getProgressBar() + { + if (!$this->progressBar) { + throw new RuntimeException('The ProgressBar is not started.'); + } + + return $this->progressBar; + } + + private function autoPrependBlock() + { + $chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); + + if (!isset($chars[0])) { + return $this->newLine(); //empty history, so we should start with a new line. + } + //Prepend new line for each non LF chars (This means no blank line was output before) + $this->newLine(2 - substr_count($chars, "\n")); + } + + private function autoPrependText() + { + $fetched = $this->bufferedOutput->fetch(); + //Prepend new line if last char isn't EOL: + if ("\n" !== substr($fetched, -1)) { + $this->newLine(); + } + } + + private function reduceBuffer($messages) + { + // We need to know if the two last chars are PHP_EOL + // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer + return array_map(function ($value) { + return substr($value, -4); + }, array_merge(array($this->bufferedOutput->fetch()), (array) $messages)); + } + + private function createBlock($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = false) + { + $indentLength = 0; + $prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix); + $lines = array(); + + if (null !== $type) { + $type = sprintf('[%s] ', $type); + $indentLength = strlen($type); + $lineIndentation = str_repeat(' ', $indentLength); + } + + // wrap and add newlines for each element + foreach ($messages as $key => $message) { + if ($escape) { + $message = OutputFormatter::escape($message); + } + + $lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true))); + + if (count($messages) > 1 && $key < count($messages) - 1) { + $lines[] = ''; + } + } + + $firstLineIndex = 0; + if ($padding && $this->isDecorated()) { + $firstLineIndex = 1; + array_unshift($lines, ''); + $lines[] = ''; + } + + foreach ($lines as $i => &$line) { + if (null !== $type) { + $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line; + } + + $line = $prefix.$line; + $line .= str_repeat(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line)); + + if ($style) { + $line = sprintf('<%s>%s', $style, $line); + } + } + + return $lines; + } +} diff --git a/vendor/symfony/console/Terminal.php b/vendor/symfony/console/Terminal.php new file mode 100644 index 00000000..927dfc4d --- /dev/null +++ b/vendor/symfony/console/Terminal.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\Console; + +class Terminal +{ + private static $width; + private static $height; + + /** + * Gets the terminal width. + * + * @return int + */ + public function getWidth() + { + $width = getenv('COLUMNS'); + if (false !== $width) { + return (int) trim($width); + } + + if (null === self::$width) { + self::initDimensions(); + } + + return self::$width ?: 80; + } + + /** + * Gets the terminal height. + * + * @return int + */ + public function getHeight() + { + $height = getenv('LINES'); + if (false !== $height) { + return (int) trim($height); + } + + if (null === self::$height) { + self::initDimensions(); + } + + return self::$height ?: 50; + } + + private static function initDimensions() + { + if ('\\' === DIRECTORY_SEPARATOR) { + if (preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim(getenv('ANSICON')), $matches)) { + // extract [w, H] from "wxh (WxH)" + // or [w, h] from "wxh" + self::$width = (int) $matches[1]; + self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2]; + } elseif (null !== $dimensions = self::getConsoleMode()) { + // extract [w, h] from "wxh" + self::$width = (int) $dimensions[0]; + self::$height = (int) $dimensions[1]; + } + } elseif ($sttyString = self::getSttyColumns()) { + if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { + // extract [w, h] from "rows h; columns w;" + self::$width = (int) $matches[2]; + self::$height = (int) $matches[1]; + } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { + // extract [w, h] from "; h rows; w columns" + self::$width = (int) $matches[2]; + self::$height = (int) $matches[1]; + } + } + } + + /** + * Runs and parses mode CON if it's available, suppressing any error output. + * + * @return int[]|null An array composed of the width and the height or null if it could not be parsed + */ + private static function getConsoleMode() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = array( + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w'), + ); + $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + return array((int) $matches[2], (int) $matches[1]); + } + } + } + + /** + * Runs and parses stty -a if it's available, suppressing any error output. + * + * @return string|null + */ + private static function getSttyColumns() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = array( + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w'), + ); + + $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + return $info; + } + } +} diff --git a/vendor/symfony/console/Tester/ApplicationTester.php b/vendor/symfony/console/Tester/ApplicationTester.php new file mode 100644 index 00000000..c0f8c720 --- /dev/null +++ b/vendor/symfony/console/Tester/ApplicationTester.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\StreamOutput; + +/** + * Eases the testing of console applications. + * + * When testing an application, don't forget to disable the auto exit flag: + * + * $application = new Application(); + * $application->setAutoExit(false); + * + * @author Fabien Potencier + */ +class ApplicationTester +{ + private $application; + private $input; + private $statusCode; + /** + * @var OutputInterface + */ + private $output; + private $captureStreamsIndependently = false; + + public function __construct(Application $application) + { + $this->application = $application; + } + + /** + * Executes the application. + * + * Available options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * * capture_stderr_separately: Make output of stdOut and stdErr separately available + * + * @param array $input An array of arguments and options + * @param array $options An array of options + * + * @return int The command exit code + */ + public function run(array $input, $options = array()) + { + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + $this->captureStreamsIndependently = array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately']; + if (!$this->captureStreamsIndependently) { + $this->output = new StreamOutput(fopen('php://memory', 'w', false)); + if (isset($options['decorated'])) { + $this->output->setDecorated($options['decorated']); + } + if (isset($options['verbosity'])) { + $this->output->setVerbosity($options['verbosity']); + } + } else { + $this->output = new ConsoleOutput( + isset($options['verbosity']) ? $options['verbosity'] : ConsoleOutput::VERBOSITY_NORMAL, + isset($options['decorated']) ? $options['decorated'] : null + ); + + $errorOutput = new StreamOutput(fopen('php://memory', 'w', false)); + $errorOutput->setFormatter($this->output->getFormatter()); + $errorOutput->setVerbosity($this->output->getVerbosity()); + $errorOutput->setDecorated($this->output->isDecorated()); + + $reflectedOutput = new \ReflectionObject($this->output); + $strErrProperty = $reflectedOutput->getProperty('stderr'); + $strErrProperty->setAccessible(true); + $strErrProperty->setValue($this->output, $errorOutput); + + $reflectedParent = $reflectedOutput->getParentClass(); + $streamProperty = $reflectedParent->getProperty('stream'); + $streamProperty->setAccessible(true); + $streamProperty->setValue($this->output, fopen('php://memory', 'w', false)); + } + + return $this->statusCode = $this->application->run($this->input, $this->output); + } + + /** + * Gets the display returned by the last execution of the application. + * + * @param bool $normalize Whether to normalize end of lines to \n or not + * + * @return string The display + */ + public function getDisplay($normalize = false) + { + rewind($this->output->getStream()); + + $display = stream_get_contents($this->output->getStream()); + + if ($normalize) { + $display = str_replace(PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the output written to STDERR by the application. + * + * @param bool $normalize Whether to normalize end of lines to \n or not + * + * @return string + */ + public function getErrorOutput($normalize = false) + { + if (!$this->captureStreamsIndependently) { + throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.'); + } + + rewind($this->output->getErrorOutput()->getStream()); + + $display = stream_get_contents($this->output->getErrorOutput()->getStream()); + + if ($normalize) { + $display = str_replace(PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the input instance used by the last execution of the application. + * + * @return InputInterface The current input instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the application. + * + * @return OutputInterface The current output instance + */ + public function getOutput() + { + return $this->output; + } + + /** + * Gets the status code returned by the last execution of the application. + * + * @return int The status code + */ + public function getStatusCode() + { + return $this->statusCode; + } +} diff --git a/vendor/symfony/console/Tester/CommandTester.php b/vendor/symfony/console/Tester/CommandTester.php new file mode 100644 index 00000000..0bb1603c --- /dev/null +++ b/vendor/symfony/console/Tester/CommandTester.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\Console\Tester; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Eases the testing of console commands. + * + * @author Fabien Potencier + * @author Robin Chalas + */ +class CommandTester +{ + private $command; + private $input; + private $output; + private $inputs = array(); + private $statusCode; + + /** + * Constructor. + * + * @param Command $command A Command instance to test + */ + public function __construct(Command $command) + { + $this->command = $command; + } + + /** + * Executes the command. + * + * Available execution options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * + * @param array $input An array of command arguments and options + * @param array $options An array of execution options + * + * @return int The command exit code + */ + public function execute(array $input, array $options = array()) + { + // set the command name automatically if the application requires + // this argument and no command name was passed + if (!isset($input['command']) + && (null !== $application = $this->command->getApplication()) + && $application->getDefinition()->hasArgument('command') + ) { + $input = array_merge(array('command' => $this->command->getName()), $input); + } + + $this->input = new ArrayInput($input); + if ($this->inputs) { + $this->input->setStream(self::createStream($this->inputs)); + } + + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + $this->output = new StreamOutput(fopen('php://memory', 'w', false)); + $this->output->setDecorated(isset($options['decorated']) ? $options['decorated'] : false); + if (isset($options['verbosity'])) { + $this->output->setVerbosity($options['verbosity']); + } + + return $this->statusCode = $this->command->run($this->input, $this->output); + } + + /** + * Gets the display returned by the last execution of the command. + * + * @param bool $normalize Whether to normalize end of lines to \n or not + * + * @return string The display + */ + public function getDisplay($normalize = false) + { + rewind($this->output->getStream()); + + $display = stream_get_contents($this->output->getStream()); + + if ($normalize) { + $display = str_replace(PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the input instance used by the last execution of the command. + * + * @return InputInterface The current input instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the command. + * + * @return OutputInterface The current output instance + */ + public function getOutput() + { + return $this->output; + } + + /** + * Gets the status code returned by the last execution of the application. + * + * @return int The status code + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Sets the user inputs. + * + * @param array An array of strings representing each input + * passed to the command input stream. + * + * @return CommandTester + */ + public function setInputs(array $inputs) + { + $this->inputs = $inputs; + + return $this; + } + + private static function createStream(array $inputs) + { + $stream = fopen('php://memory', 'r+', false); + + fwrite($stream, implode(PHP_EOL, $inputs)); + rewind($stream); + + return $stream; + } +} diff --git a/vendor/symfony/console/Tests/ApplicationTest.php b/vendor/symfony/console/Tests/ApplicationTest.php new file mode 100644 index 00000000..34d9cb0c --- /dev/null +++ b/vendor/symfony/console/Tests/ApplicationTest.php @@ -0,0 +1,1451 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\Console\Output\Output; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Tester\ApplicationTester; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleExceptionEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class ApplicationTest extends TestCase +{ + protected static $fixturesPath; + + public static function setUpBeforeClass() + { + self::$fixturesPath = realpath(__DIR__.'/Fixtures/'); + require_once self::$fixturesPath.'/FooCommand.php'; + require_once self::$fixturesPath.'/Foo1Command.php'; + require_once self::$fixturesPath.'/Foo2Command.php'; + require_once self::$fixturesPath.'/Foo3Command.php'; + require_once self::$fixturesPath.'/Foo4Command.php'; + require_once self::$fixturesPath.'/Foo5Command.php'; + require_once self::$fixturesPath.'/FoobarCommand.php'; + require_once self::$fixturesPath.'/BarBucCommand.php'; + require_once self::$fixturesPath.'/FooSubnamespaced1Command.php'; + require_once self::$fixturesPath.'/FooSubnamespaced2Command.php'; + } + + protected function normalizeLineBreaks($text) + { + return str_replace(PHP_EOL, "\n", $text); + } + + /** + * Replaces the dynamic placeholders of the command help text with a static version. + * The placeholder %command.full_name% includes the script path that is not predictable + * and can not be tested against. + */ + protected function ensureStaticCommandHelp(Application $application) + { + foreach ($application->all() as $command) { + $command->setHelp(str_replace('%command.full_name%', 'app/console %command.name%', $command->getHelp())); + } + } + + public function testConstructor() + { + $application = new Application('foo', 'bar'); + $this->assertEquals('foo', $application->getName(), '__construct() takes the application name as its first argument'); + $this->assertEquals('bar', $application->getVersion(), '__construct() takes the application version as its second argument'); + $this->assertEquals(array('help', 'list'), array_keys($application->all()), '__construct() registered the help and list commands by default'); + } + + public function testSetGetName() + { + $application = new Application(); + $application->setName('foo'); + $this->assertEquals('foo', $application->getName(), '->setName() sets the name of the application'); + } + + public function testSetGetVersion() + { + $application = new Application(); + $application->setVersion('bar'); + $this->assertEquals('bar', $application->getVersion(), '->setVersion() sets the version of the application'); + } + + public function testGetLongVersion() + { + $application = new Application('foo', 'bar'); + $this->assertEquals('foo bar', $application->getLongVersion(), '->getLongVersion() returns the long version of the application'); + } + + public function testHelp() + { + $application = new Application(); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_gethelp.txt', $this->normalizeLineBreaks($application->getHelp()), '->getHelp() returns a help message'); + } + + public function testAll() + { + $application = new Application(); + $commands = $application->all(); + $this->assertInstanceOf('Symfony\\Component\\Console\\Command\\HelpCommand', $commands['help'], '->all() returns the registered commands'); + + $application->add(new \FooCommand()); + $commands = $application->all('foo'); + $this->assertCount(1, $commands, '->all() takes a namespace as its first argument'); + } + + public function testRegister() + { + $application = new Application(); + $command = $application->register('foo'); + $this->assertEquals('foo', $command->getName(), '->register() registers a new command'); + } + + public function testAdd() + { + $application = new Application(); + $application->add($foo = new \FooCommand()); + $commands = $application->all(); + $this->assertEquals($foo, $commands['foo:bar'], '->add() registers a command'); + + $application = new Application(); + $application->addCommands(array($foo = new \FooCommand(), $foo1 = new \Foo1Command())); + $commands = $application->all(); + $this->assertEquals(array($foo, $foo1), array($commands['foo:bar'], $commands['foo:bar1']), '->addCommands() registers an array of commands'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Command class "Foo5Command" is not correctly initialized. You probably forgot to call the parent constructor. + */ + public function testAddCommandWithEmptyConstructor() + { + $application = new Application(); + $application->add(new \Foo5Command()); + } + + public function testHasGet() + { + $application = new Application(); + $this->assertTrue($application->has('list'), '->has() returns true if a named command is registered'); + $this->assertFalse($application->has('afoobar'), '->has() returns false if a named command is not registered'); + + $application->add($foo = new \FooCommand()); + $this->assertTrue($application->has('afoobar'), '->has() returns true if an alias is registered'); + $this->assertEquals($foo, $application->get('foo:bar'), '->get() returns a command by name'); + $this->assertEquals($foo, $application->get('afoobar'), '->get() returns a command by alias'); + + $application = new Application(); + $application->add($foo = new \FooCommand()); + // simulate --help + $r = new \ReflectionObject($application); + $p = $r->getProperty('wantHelps'); + $p->setAccessible(true); + $p->setValue($application, true); + $command = $application->get('foo:bar'); + $this->assertInstanceOf('Symfony\Component\Console\Command\HelpCommand', $command, '->get() returns the help command if --help is provided as the input'); + } + + public function testSilentHelp() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $tester = new ApplicationTester($application); + $tester->run(array('-h' => true, '-q' => true), array('decorated' => false)); + + $this->assertEmpty($tester->getDisplay(true)); + } + + /** + * @expectedException \Symfony\Component\Console\Exception\CommandNotFoundException + * @expectedExceptionMessage The command "foofoo" does not exist. + */ + public function testGetInvalidCommand() + { + $application = new Application(); + $application->get('foofoo'); + } + + public function testGetNamespaces() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $this->assertEquals(array('foo'), $application->getNamespaces(), '->getNamespaces() returns an array of unique used namespaces'); + } + + public function testFindNamespace() + { + $application = new Application(); + $application->add(new \FooCommand()); + $this->assertEquals('foo', $application->findNamespace('foo'), '->findNamespace() returns the given namespace if it exists'); + $this->assertEquals('foo', $application->findNamespace('f'), '->findNamespace() finds a namespace given an abbreviation'); + $application->add(new \Foo2Command()); + $this->assertEquals('foo', $application->findNamespace('foo'), '->findNamespace() returns the given namespace if it exists'); + } + + public function testFindNamespaceWithSubnamespaces() + { + $application = new Application(); + $application->add(new \FooSubnamespaced1Command()); + $application->add(new \FooSubnamespaced2Command()); + $this->assertEquals('foo', $application->findNamespace('foo'), '->findNamespace() returns commands even if the commands are only contained in subnamespaces'); + } + + public function testFindAmbiguousNamespace() + { + $application = new Application(); + $application->add(new \BarBucCommand()); + $application->add(new \FooCommand()); + $application->add(new \Foo2Command()); + + $expectedMsg = "The namespace \"f\" is ambiguous.\nDid you mean one of these?\n foo\n foo1"; + + if (method_exists($this, 'expectException')) { + $this->expectException(CommandNotFoundException::class); + $this->expectExceptionMessage($expectedMsg); + } else { + $this->setExpectedException(CommandNotFoundException::class, $expectedMsg); + } + + $application->findNamespace('f'); + } + + /** + * @expectedException \Symfony\Component\Console\Exception\CommandNotFoundException + * @expectedExceptionMessage There are no commands defined in the "bar" namespace. + */ + public function testFindInvalidNamespace() + { + $application = new Application(); + $application->findNamespace('bar'); + } + + /** + * @expectedException \Symfony\Component\Console\Exception\CommandNotFoundException + * @expectedExceptionMessage Command "foo1" is not defined + */ + public function testFindUniqueNameButNamespaceName() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + + $application->find($commandName = 'foo1'); + } + + public function testFind() + { + $application = new Application(); + $application->add(new \FooCommand()); + + $this->assertInstanceOf('FooCommand', $application->find('foo:bar'), '->find() returns a command if its name exists'); + $this->assertInstanceOf('Symfony\Component\Console\Command\HelpCommand', $application->find('h'), '->find() returns a command if its name exists'); + $this->assertInstanceOf('FooCommand', $application->find('f:bar'), '->find() returns a command if the abbreviation for the namespace exists'); + $this->assertInstanceOf('FooCommand', $application->find('f:b'), '->find() returns a command if the abbreviation for the namespace and the command name exist'); + $this->assertInstanceOf('FooCommand', $application->find('a'), '->find() returns a command if the abbreviation exists for an alias'); + } + + /** + * @dataProvider provideAmbiguousAbbreviations + */ + public function testFindWithAmbiguousAbbreviations($abbreviation, $expectedExceptionMessage) + { + if (method_exists($this, 'expectException')) { + $this->expectException('Symfony\Component\Console\Exception\CommandNotFoundException'); + $this->expectExceptionMessage($expectedExceptionMessage); + } else { + $this->setExpectedException('Symfony\Component\Console\Exception\CommandNotFoundException', $expectedExceptionMessage); + } + + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + + $application->find($abbreviation); + } + + public function provideAmbiguousAbbreviations() + { + return array( + array('f', 'Command "f" is not defined.'), + array( + 'a', + "Command \"a\" is ambiguous.\nDid you mean one of these?\n". + " afoobar The foo:bar command\n". + " afoobar1 The foo:bar1 command\n". + ' afoobar2 The foo1:bar command', + ), + array( + 'foo:b', + "Command \"foo:b\" is ambiguous.\nDid you mean one of these?\n". + " foo:bar The foo:bar command\n". + " foo:bar1 The foo:bar1 command\n". + ' foo1:bar The foo1:bar command', + ), + ); + } + + public function testFindCommandEqualNamespace() + { + $application = new Application(); + $application->add(new \Foo3Command()); + $application->add(new \Foo4Command()); + + $this->assertInstanceOf('Foo3Command', $application->find('foo3:bar'), '->find() returns the good command even if a namespace has same name'); + $this->assertInstanceOf('Foo4Command', $application->find('foo3:bar:toh'), '->find() returns a command even if its namespace equals another command name'); + } + + public function testFindCommandWithAmbiguousNamespacesButUniqueName() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \FoobarCommand()); + + $this->assertInstanceOf('FoobarCommand', $application->find('f:f')); + } + + public function testFindCommandWithMissingNamespace() + { + $application = new Application(); + $application->add(new \Foo4Command()); + + $this->assertInstanceOf('Foo4Command', $application->find('f::t')); + } + + /** + * @dataProvider provideInvalidCommandNamesSingle + * @expectedException \Symfony\Component\Console\Exception\CommandNotFoundException + * @expectedExceptionMessage Did you mean this + */ + public function testFindAlternativeExceptionMessageSingle($name) + { + $application = new Application(); + $application->add(new \Foo3Command()); + $application->find($name); + } + + public function provideInvalidCommandNamesSingle() + { + return array( + array('foo3:baR'), + array('foO3:bar'), + ); + } + + public function testFindAlternativeExceptionMessageMultiple() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + + // Command + plural + try { + $application->find('foo:baR'); + $this->fail('->find() throws a CommandNotFoundException if command does not exist, with alternatives'); + } catch (\Exception $e) { + $this->assertInstanceOf('Symfony\Component\Console\Exception\CommandNotFoundException', $e, '->find() throws a CommandNotFoundException if command does not exist, with alternatives'); + $this->assertRegExp('/Did you mean one of these/', $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, with alternatives'); + $this->assertRegExp('/foo1:bar/', $e->getMessage()); + $this->assertRegExp('/foo:bar/', $e->getMessage()); + } + + // Namespace + plural + try { + $application->find('foo2:bar'); + $this->fail('->find() throws a CommandNotFoundException if command does not exist, with alternatives'); + } catch (\Exception $e) { + $this->assertInstanceOf('Symfony\Component\Console\Exception\CommandNotFoundException', $e, '->find() throws a CommandNotFoundException if command does not exist, with alternatives'); + $this->assertRegExp('/Did you mean one of these/', $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, with alternatives'); + $this->assertRegExp('/foo1/', $e->getMessage()); + } + + $application->add(new \Foo3Command()); + $application->add(new \Foo4Command()); + + // Subnamespace + plural + try { + $a = $application->find('foo3:'); + $this->fail('->find() should throw an Symfony\Component\Console\Exception\CommandNotFoundException if a command is ambiguous because of a subnamespace, with alternatives'); + } catch (\Exception $e) { + $this->assertInstanceOf('Symfony\Component\Console\Exception\CommandNotFoundException', $e); + $this->assertRegExp('/foo3:bar/', $e->getMessage()); + $this->assertRegExp('/foo3:bar:toh/', $e->getMessage()); + } + } + + public function testFindAlternativeCommands() + { + $application = new Application(); + + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + + try { + $application->find($commandName = 'Unknown command'); + $this->fail('->find() throws a CommandNotFoundException if command does not exist'); + } catch (\Exception $e) { + $this->assertInstanceOf('Symfony\Component\Console\Exception\CommandNotFoundException', $e, '->find() throws a CommandNotFoundException if command does not exist'); + $this->assertSame(array(), $e->getAlternatives()); + $this->assertEquals(sprintf('Command "%s" is not defined.', $commandName), $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, without alternatives'); + } + + // Test if "bar1" command throw a "CommandNotFoundException" and does not contain + // "foo:bar" as alternative because "bar1" is too far from "foo:bar" + try { + $application->find($commandName = 'bar1'); + $this->fail('->find() throws a CommandNotFoundException if command does not exist'); + } catch (\Exception $e) { + $this->assertInstanceOf('Symfony\Component\Console\Exception\CommandNotFoundException', $e, '->find() throws a CommandNotFoundException if command does not exist'); + $this->assertSame(array('afoobar1', 'foo:bar1'), $e->getAlternatives()); + $this->assertRegExp(sprintf('/Command "%s" is not defined./', $commandName), $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, with alternatives'); + $this->assertRegExp('/afoobar1/', $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, with alternative : "afoobar1"'); + $this->assertRegExp('/foo:bar1/', $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, with alternative : "foo:bar1"'); + $this->assertNotRegExp('/foo:bar(?>!1)/', $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, without "foo:bar" alternative'); + } + } + + public function testFindAlternativeCommandsWithAnAlias() + { + $fooCommand = new \FooCommand(); + $fooCommand->setAliases(array('foo2')); + + $application = new Application(); + $application->add($fooCommand); + + $result = $application->find('foo'); + + $this->assertSame($fooCommand, $result); + } + + public function testFindAlternativeNamespace() + { + $application = new Application(); + + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + $application->add(new \Foo3Command()); + + try { + $application->find('Unknown-namespace:Unknown-command'); + $this->fail('->find() throws a CommandNotFoundException if namespace does not exist'); + } catch (\Exception $e) { + $this->assertInstanceOf('Symfony\Component\Console\Exception\CommandNotFoundException', $e, '->find() throws a CommandNotFoundException if namespace does not exist'); + $this->assertSame(array(), $e->getAlternatives()); + $this->assertEquals('There are no commands defined in the "Unknown-namespace" namespace.', $e->getMessage(), '->find() throws a CommandNotFoundException if namespace does not exist, without alternatives'); + } + + try { + $application->find('foo2:command'); + $this->fail('->find() throws a CommandNotFoundException if namespace does not exist'); + } catch (\Exception $e) { + $this->assertInstanceOf('Symfony\Component\Console\Exception\CommandNotFoundException', $e, '->find() throws a CommandNotFoundException if namespace does not exist'); + $this->assertCount(3, $e->getAlternatives()); + $this->assertContains('foo', $e->getAlternatives()); + $this->assertContains('foo1', $e->getAlternatives()); + $this->assertContains('foo3', $e->getAlternatives()); + $this->assertRegExp('/There are no commands defined in the "foo2" namespace./', $e->getMessage(), '->find() throws a CommandNotFoundException if namespace does not exist, with alternative'); + $this->assertRegExp('/foo/', $e->getMessage(), '->find() throws a CommandNotFoundException if namespace does not exist, with alternative : "foo"'); + $this->assertRegExp('/foo1/', $e->getMessage(), '->find() throws a CommandNotFoundException if namespace does not exist, with alternative : "foo1"'); + $this->assertRegExp('/foo3/', $e->getMessage(), '->find() throws a CommandNotFoundException if namespace does not exist, with alternative : "foo3"'); + } + } + + public function testFindAlternativesOutput() + { + $application = new Application(); + + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + $application->add(new \Foo3Command()); + + $expectedAlternatives = array( + 'afoobar', + 'afoobar1', + 'afoobar2', + 'foo1:bar', + 'foo3:bar', + 'foo:bar', + 'foo:bar1', + ); + + try { + $application->find('foo'); + $this->fail('->find() throws a CommandNotFoundException if command is not defined'); + } catch (\Exception $e) { + $this->assertInstanceOf('Symfony\Component\Console\Exception\CommandNotFoundException', $e, '->find() throws a CommandNotFoundException if command is not defined'); + $this->assertSame($expectedAlternatives, $e->getAlternatives()); + + $this->assertRegExp('/Command "foo" is not defined\..*Did you mean one of these\?.*/Ums', $e->getMessage()); + } + } + + public function testFindNamespaceDoesNotFailOnDeepSimilarNamespaces() + { + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->setMethods(array('getNamespaces'))->getMock(); + $application->expects($this->once()) + ->method('getNamespaces') + ->will($this->returnValue(array('foo:sublong', 'bar:sub'))); + + $this->assertEquals('foo:sublong', $application->findNamespace('f:sub')); + } + + /** + * @expectedException \Symfony\Component\Console\Exception\CommandNotFoundException + * @expectedExceptionMessage Command "foo::bar" is not defined. + */ + public function testFindWithDoubleColonInNameThrowsException() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo4Command()); + $application->find('foo::bar'); + } + + public function testSetCatchExceptions() + { + $application = new Application(); + $application->setAutoExit(false); + putenv('COLUMNS=120'); + $tester = new ApplicationTester($application); + + $application->setCatchExceptions(true); + $this->assertTrue($application->areExceptionsCaught()); + + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getDisplay(true), '->setCatchExceptions() sets the catch exception flag'); + + $tester->run(array('command' => 'foo'), array('decorated' => false, 'capture_stderr_separately' => true)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getErrorOutput(true), '->setCatchExceptions() sets the catch exception flag'); + $this->assertSame('', $tester->getDisplay(true)); + + $application->setCatchExceptions(false); + try { + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->fail('->setCatchExceptions() sets the catch exception flag'); + } catch (\Exception $e) { + $this->assertInstanceOf('\Exception', $e, '->setCatchExceptions() sets the catch exception flag'); + $this->assertEquals('Command "foo" is not defined.', $e->getMessage(), '->setCatchExceptions() sets the catch exception flag'); + } + } + + public function testAutoExitSetting() + { + $application = new Application(); + $this->assertTrue($application->isAutoExitEnabled()); + + $application->setAutoExit(false); + $this->assertFalse($application->isAutoExitEnabled()); + } + + public function testRenderException() + { + $application = new Application(); + $application->setAutoExit(false); + putenv('COLUMNS=120'); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo'), array('decorated' => false, 'capture_stderr_separately' => true)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getErrorOutput(true), '->renderException() renders a pretty exception'); + + $tester->run(array('command' => 'foo'), array('decorated' => false, 'verbosity' => Output::VERBOSITY_VERBOSE, 'capture_stderr_separately' => true)); + $this->assertContains('Exception trace', $tester->getErrorOutput(), '->renderException() renders a pretty exception with a stack trace when verbosity is verbose'); + + $tester->run(array('command' => 'list', '--foo' => true), array('decorated' => false, 'capture_stderr_separately' => true)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception2.txt', $tester->getErrorOutput(true), '->renderException() renders the command synopsis when an exception occurs in the context of a command'); + + $application->add(new \Foo3Command()); + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo3:bar'), array('decorated' => false, 'capture_stderr_separately' => true)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3.txt', $tester->getErrorOutput(true), '->renderException() renders a pretty exceptions with previous exceptions'); + + $tester->run(array('command' => 'foo3:bar'), array('decorated' => false, 'verbosity' => Output::VERBOSITY_VERBOSE)); + $this->assertRegExp('/\[Exception\]\s*First exception/', $tester->getDisplay(), '->renderException() renders a pretty exception without code exception when code exception is default and verbosity is verbose'); + $this->assertRegExp('/\[Exception\]\s*Second exception/', $tester->getDisplay(), '->renderException() renders a pretty exception without code exception when code exception is 0 and verbosity is verbose'); + $this->assertRegExp('/\[Exception \(404\)\]\s*Third exception/', $tester->getDisplay(), '->renderException() renders a pretty exception with code exception when code exception is 404 and verbosity is verbose'); + + $tester->run(array('command' => 'foo3:bar'), array('decorated' => true)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3decorated.txt', $tester->getDisplay(true), '->renderException() renders a pretty exceptions with previous exceptions'); + + $tester->run(array('command' => 'foo3:bar'), array('decorated' => true, 'capture_stderr_separately' => true)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3decorated.txt', $tester->getErrorOutput(true), '->renderException() renders a pretty exceptions with previous exceptions'); + + $application = new Application(); + $application->setAutoExit(false); + putenv('COLUMNS=32'); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo'), array('decorated' => false, 'capture_stderr_separately' => true)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception4.txt', $tester->getErrorOutput(true), '->renderException() wraps messages when they are bigger than the terminal'); + putenv('COLUMNS=120'); + } + + public function testRenderExceptionWithDoubleWidthCharacters() + { + $application = new Application(); + $application->setAutoExit(false); + putenv('COLUMNS=120'); + $application->register('foo')->setCode(function () { + throw new \Exception('エラーメッセージ'); + }); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo'), array('decorated' => false, 'capture_stderr_separately' => true)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1.txt', $tester->getErrorOutput(true), '->renderException() renders a pretty exceptions with previous exceptions'); + + $tester->run(array('command' => 'foo'), array('decorated' => true, 'capture_stderr_separately' => true)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1decorated.txt', $tester->getErrorOutput(true), '->renderException() renders a pretty exceptions with previous exceptions'); + + $application = new Application(); + $application->setAutoExit(false); + putenv('COLUMNS=32'); + $application->register('foo')->setCode(function () { + throw new \Exception('コマンドの実行中にエラーが発生しました。'); + }); + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo'), array('decorated' => false, 'capture_stderr_separately' => true)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth2.txt', $tester->getErrorOutput(true), '->renderException() wraps messages when they are bigger than the terminal'); + putenv('COLUMNS=120'); + } + + public function testRenderExceptionEscapesLines() + { + $application = new Application(); + $application->setAutoExit(false); + putenv('COLUMNS=22'); + $application->register('foo')->setCode(function () { + throw new \Exception('dont break here !'); + }); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_escapeslines.txt', $tester->getDisplay(true), '->renderException() escapes lines containing formatting'); + putenv('COLUMNS=120'); + } + + public function testRun() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application->add($command = new \Foo1Command()); + $_SERVER['argv'] = array('cli.php', 'foo:bar1'); + + ob_start(); + $application->run(); + ob_end_clean(); + + $this->assertInstanceOf('Symfony\Component\Console\Input\ArgvInput', $command->input, '->run() creates an ArgvInput by default if none is given'); + $this->assertInstanceOf('Symfony\Component\Console\Output\ConsoleOutput', $command->output, '->run() creates a ConsoleOutput by default if none is given'); + + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $this->ensureStaticCommandHelp($application); + $tester = new ApplicationTester($application); + + $tester->run(array(), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run1.txt', $tester->getDisplay(true), '->run() runs the list command if no argument is passed'); + + $tester->run(array('--help' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run2.txt', $tester->getDisplay(true), '->run() runs the help command if --help is passed'); + + $tester->run(array('-h' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run2.txt', $tester->getDisplay(true), '->run() runs the help command if -h is passed'); + + $tester->run(array('command' => 'list', '--help' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run3.txt', $tester->getDisplay(true), '->run() displays the help if --help is passed'); + + $tester->run(array('command' => 'list', '-h' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run3.txt', $tester->getDisplay(true), '->run() displays the help if -h is passed'); + + $tester->run(array('--ansi' => true)); + $this->assertTrue($tester->getOutput()->isDecorated(), '->run() forces color output if --ansi is passed'); + + $tester->run(array('--no-ansi' => true)); + $this->assertFalse($tester->getOutput()->isDecorated(), '->run() forces color output to be disabled if --no-ansi is passed'); + + $tester->run(array('--version' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run4.txt', $tester->getDisplay(true), '->run() displays the program version if --version is passed'); + + $tester->run(array('-V' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run4.txt', $tester->getDisplay(true), '->run() displays the program version if -v is passed'); + + $tester->run(array('command' => 'list', '--quiet' => true)); + $this->assertSame('', $tester->getDisplay(), '->run() removes all output if --quiet is passed'); + $this->assertFalse($tester->getInput()->isInteractive(), '->run() sets off the interactive mode if --quiet is passed'); + + $tester->run(array('command' => 'list', '-q' => true)); + $this->assertSame('', $tester->getDisplay(), '->run() removes all output if -q is passed'); + $this->assertFalse($tester->getInput()->isInteractive(), '->run() sets off the interactive mode if -q is passed'); + + $tester->run(array('command' => 'list', '--verbose' => true)); + $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if --verbose is passed'); + + $tester->run(array('command' => 'list', '--verbose' => 1)); + $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if --verbose=1 is passed'); + + $tester->run(array('command' => 'list', '--verbose' => 2)); + $this->assertSame(Output::VERBOSITY_VERY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to very verbose if --verbose=2 is passed'); + + $tester->run(array('command' => 'list', '--verbose' => 3)); + $this->assertSame(Output::VERBOSITY_DEBUG, $tester->getOutput()->getVerbosity(), '->run() sets the output to debug if --verbose=3 is passed'); + + $tester->run(array('command' => 'list', '--verbose' => 4)); + $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if unknown --verbose level is passed'); + + $tester->run(array('command' => 'list', '-v' => true)); + $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if -v is passed'); + + $tester->run(array('command' => 'list', '-vv' => true)); + $this->assertSame(Output::VERBOSITY_VERY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if -v is passed'); + + $tester->run(array('command' => 'list', '-vvv' => true)); + $this->assertSame(Output::VERBOSITY_DEBUG, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if -v is passed'); + + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application->add(new \FooCommand()); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo:bar', '--no-interaction' => true), array('decorated' => false)); + $this->assertSame('called'.PHP_EOL, $tester->getDisplay(), '->run() does not call interact() if --no-interaction is passed'); + + $tester->run(array('command' => 'foo:bar', '-n' => true), array('decorated' => false)); + $this->assertSame('called'.PHP_EOL, $tester->getDisplay(), '->run() does not call interact() if -n is passed'); + } + + /** + * Issue #9285. + * + * If the "verbose" option is just before an argument in ArgvInput, + * an argument value should not be treated as verbosity value. + * This test will fail with "Not enough arguments." if broken + */ + public function testVerboseValueNotBreakArguments() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application->add(new \FooCommand()); + + $output = new StreamOutput(fopen('php://memory', 'w', false)); + + $input = new ArgvInput(array('cli.php', '-v', 'foo:bar')); + $application->run($input, $output); + + $this->addToAssertionCount(1); + + $input = new ArgvInput(array('cli.php', '--verbose', 'foo:bar')); + $application->run($input, $output); + + $this->addToAssertionCount(1); + } + + public function testRunReturnsIntegerExitCode() + { + $exception = new \Exception('', 4); + + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->setMethods(array('doRun'))->getMock(); + $application->setAutoExit(false); + $application->expects($this->once()) + ->method('doRun') + ->will($this->throwException($exception)); + + $exitCode = $application->run(new ArrayInput(array()), new NullOutput()); + + $this->assertSame(4, $exitCode, '->run() returns integer exit code extracted from raised exception'); + } + + public function testRunReturnsExitCodeOneForExceptionCodeZero() + { + $exception = new \Exception('', 0); + + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->setMethods(array('doRun'))->getMock(); + $application->setAutoExit(false); + $application->expects($this->once()) + ->method('doRun') + ->will($this->throwException($exception)); + + $exitCode = $application->run(new ArrayInput(array()), new NullOutput()); + + $this->assertSame(1, $exitCode, '->run() returns exit code 1 when exception code is 0'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage An option with shortcut "e" already exists. + */ + public function testAddingOptionWithDuplicateShortcut() + { + $dispatcher = new EventDispatcher(); + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application->setDispatcher($dispatcher); + + $application->getDefinition()->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'Environment')); + + $application + ->register('foo') + ->setAliases(array('f')) + ->setDefinition(array(new InputOption('survey', 'e', InputOption::VALUE_REQUIRED, 'My option with a shortcut.'))) + ->setCode(function (InputInterface $input, OutputInterface $output) {}) + ; + + $input = new ArrayInput(array('command' => 'foo')); + $output = new NullOutput(); + + $application->run($input, $output); + } + + /** + * @expectedException \LogicException + * @dataProvider getAddingAlreadySetDefinitionElementData + */ + public function testAddingAlreadySetDefinitionElementData($def) + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application + ->register('foo') + ->setDefinition(array($def)) + ->setCode(function (InputInterface $input, OutputInterface $output) {}) + ; + + $input = new ArrayInput(array('command' => 'foo')); + $output = new NullOutput(); + $application->run($input, $output); + } + + public function getAddingAlreadySetDefinitionElementData() + { + return array( + array(new InputArgument('command', InputArgument::REQUIRED)), + array(new InputOption('quiet', '', InputOption::VALUE_NONE)), + array(new InputOption('query', 'q', InputOption::VALUE_NONE)), + ); + } + + public function testGetDefaultHelperSetReturnsDefaultValues() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $helperSet = $application->getHelperSet(); + + $this->assertTrue($helperSet->has('formatter')); + } + + public function testAddingSingleHelperSetOverwritesDefaultValues() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->setHelperSet(new HelperSet(array(new FormatterHelper()))); + + $helperSet = $application->getHelperSet(); + + $this->assertTrue($helperSet->has('formatter')); + + // no other default helper set should be returned + $this->assertFalse($helperSet->has('dialog')); + $this->assertFalse($helperSet->has('progress')); + } + + public function testOverwritingDefaultHelperSetOverwritesDefaultValues() + { + $application = new CustomApplication(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->setHelperSet(new HelperSet(array(new FormatterHelper()))); + + $helperSet = $application->getHelperSet(); + + $this->assertTrue($helperSet->has('formatter')); + + // no other default helper set should be returned + $this->assertFalse($helperSet->has('dialog')); + $this->assertFalse($helperSet->has('progress')); + } + + public function testGetDefaultInputDefinitionReturnsDefaultValues() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $inputDefinition = $application->getDefinition(); + + $this->assertTrue($inputDefinition->hasArgument('command')); + + $this->assertTrue($inputDefinition->hasOption('help')); + $this->assertTrue($inputDefinition->hasOption('quiet')); + $this->assertTrue($inputDefinition->hasOption('verbose')); + $this->assertTrue($inputDefinition->hasOption('version')); + $this->assertTrue($inputDefinition->hasOption('ansi')); + $this->assertTrue($inputDefinition->hasOption('no-ansi')); + $this->assertTrue($inputDefinition->hasOption('no-interaction')); + } + + public function testOverwritingDefaultInputDefinitionOverwritesDefaultValues() + { + $application = new CustomApplication(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $inputDefinition = $application->getDefinition(); + + // check whether the default arguments and options are not returned any more + $this->assertFalse($inputDefinition->hasArgument('command')); + + $this->assertFalse($inputDefinition->hasOption('help')); + $this->assertFalse($inputDefinition->hasOption('quiet')); + $this->assertFalse($inputDefinition->hasOption('verbose')); + $this->assertFalse($inputDefinition->hasOption('version')); + $this->assertFalse($inputDefinition->hasOption('ansi')); + $this->assertFalse($inputDefinition->hasOption('no-ansi')); + $this->assertFalse($inputDefinition->hasOption('no-interaction')); + + $this->assertTrue($inputDefinition->hasOption('custom')); + } + + public function testSettingCustomInputDefinitionOverwritesDefaultValues() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->setDefinition(new InputDefinition(array(new InputOption('--custom', '-c', InputOption::VALUE_NONE, 'Set the custom input definition.')))); + + $inputDefinition = $application->getDefinition(); + + // check whether the default arguments and options are not returned any more + $this->assertFalse($inputDefinition->hasArgument('command')); + + $this->assertFalse($inputDefinition->hasOption('help')); + $this->assertFalse($inputDefinition->hasOption('quiet')); + $this->assertFalse($inputDefinition->hasOption('verbose')); + $this->assertFalse($inputDefinition->hasOption('version')); + $this->assertFalse($inputDefinition->hasOption('ansi')); + $this->assertFalse($inputDefinition->hasOption('no-ansi')); + $this->assertFalse($inputDefinition->hasOption('no-interaction')); + + $this->assertTrue($inputDefinition->hasOption('custom')); + } + + public function testRunWithDispatcher() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setDispatcher($this->getDispatcher()); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo')); + $this->assertEquals('before.foo.after.'.PHP_EOL, $tester->getDisplay()); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage error + */ + public function testRunWithExceptionAndDispatcher() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher()); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + throw new \RuntimeException('foo'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo')); + } + + public function testRunDispatchesAllEventsWithException() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher()); + $application->setAutoExit(false); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + + throw new \RuntimeException('foo'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo')); + $this->assertContains('before.foo.error.after.', $tester->getDisplay()); + } + + public function testRunDispatchesAllEventsWithExceptionInListener() + { + $dispatcher = $this->getDispatcher(); + $dispatcher->addListener('console.command', function () { + throw new \RuntimeException('foo'); + }); + + $application = new Application(); + $application->setDispatcher($dispatcher); + $application->setAutoExit(false); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo')); + $this->assertContains('before.error.after.', $tester->getDisplay()); + } + + public function testRunWithError() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->register('dym')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('dym.'); + + throw new \Error('dymerr'); + }); + + $tester = new ApplicationTester($application); + + try { + $tester->run(array('command' => 'dym')); + $this->fail('Error expected.'); + } catch (\Error $e) { + $this->assertSame('dymerr', $e->getMessage()); + } + } + + public function testRunAllowsErrorListenersToSilenceTheException() + { + $dispatcher = $this->getDispatcher(); + $dispatcher->addListener('console.error', function (ConsoleErrorEvent $event) { + $event->getOutput()->write('silenced.'); + + $event->setExitCode(0); + }); + + $dispatcher->addListener('console.command', function () { + throw new \RuntimeException('foo'); + }); + + $application = new Application(); + $application->setDispatcher($dispatcher); + $application->setAutoExit(false); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo')); + $this->assertContains('before.error.silenced.after.', $tester->getDisplay()); + $this->assertEquals(ConsoleCommandEvent::RETURN_CODE_DISABLED, $tester->getStatusCode()); + } + + public function testConsoleErrorEventIsTriggeredOnCommandNotFound() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener('console.error', function (ConsoleErrorEvent $event) { + $this->assertNull($event->getCommand()); + $this->assertInstanceOf(CommandNotFoundException::class, $event->getError()); + $event->getOutput()->write('silenced command not found'); + }); + + $application = new Application(); + $application->setDispatcher($dispatcher); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'unknown')); + $this->assertContains('silenced command not found', $tester->getDisplay()); + $this->assertEquals(1, $tester->getStatusCode()); + } + + /** + * @group legacy + * @expectedDeprecation The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead. + */ + public function testLegacyExceptionListenersAreStillTriggered() + { + $dispatcher = $this->getDispatcher(); + $dispatcher->addListener('console.exception', function (ConsoleExceptionEvent $event) { + $event->getOutput()->write('caught.'); + + $event->setException(new \RuntimeException('replaced in caught.')); + }); + + $application = new Application(); + $application->setDispatcher($dispatcher); + $application->setAutoExit(false); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + throw new \RuntimeException('foo'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo')); + $this->assertContains('before.caught.error.after.', $tester->getDisplay()); + $this->assertContains('replaced in caught.', $tester->getDisplay()); + } + + /** + * @requires PHP 7 + */ + public function testErrorIsRethrownIfNotHandledByConsoleErrorEvent() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application->setDispatcher(new EventDispatcher()); + + $application->register('dym')->setCode(function (InputInterface $input, OutputInterface $output) { + new \UnknownClass(); + }); + + $tester = new ApplicationTester($application); + + try { + $tester->run(array('command' => 'dym')); + $this->fail('->run() should rethrow PHP errors if not handled via ConsoleErrorEvent.'); + } catch (\Error $e) { + $this->assertSame($e->getMessage(), 'Class \'UnknownClass\' not found'); + } + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage error + */ + public function testRunWithErrorAndDispatcher() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher()); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->register('dym')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('dym.'); + + throw new \Error('dymerr'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'dym')); + $this->assertContains('before.dym.error.after.', $tester->getDisplay(), 'The PHP Error did not dispached events'); + } + + public function testRunDispatchesAllEventsWithError() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher()); + $application->setAutoExit(false); + + $application->register('dym')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('dym.'); + + throw new \Error('dymerr'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'dym')); + $this->assertContains('before.dym.error.after.', $tester->getDisplay(), 'The PHP Error did not dispached events'); + } + + public function testRunWithErrorFailingStatusCode() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher()); + $application->setAutoExit(false); + + $application->register('dus')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('dus.'); + + throw new \Error('duserr'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'dus')); + $this->assertSame(1, $tester->getStatusCode(), 'Status code should be 1'); + } + + public function testRunWithDispatcherSkippingCommand() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher(true)); + $application->setAutoExit(false); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + }); + + $tester = new ApplicationTester($application); + $exitCode = $tester->run(array('command' => 'foo')); + $this->assertContains('before.after.', $tester->getDisplay()); + $this->assertEquals(ConsoleCommandEvent::RETURN_CODE_DISABLED, $exitCode); + } + + public function testRunWithDispatcherAccessingInputOptions() + { + $noInteractionValue = null; + $quietValue = null; + + $dispatcher = $this->getDispatcher(); + $dispatcher->addListener('console.command', function (ConsoleCommandEvent $event) use (&$noInteractionValue, &$quietValue) { + $input = $event->getInput(); + + $noInteractionValue = $input->getOption('no-interaction'); + $quietValue = $input->getOption('quiet'); + }); + + $application = new Application(); + $application->setDispatcher($dispatcher); + $application->setAutoExit(false); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo', '--no-interaction' => true)); + + $this->assertTrue($noInteractionValue); + $this->assertFalse($quietValue); + } + + public function testRunWithDispatcherAddingInputOptions() + { + $extraValue = null; + + $dispatcher = $this->getDispatcher(); + $dispatcher->addListener('console.command', function (ConsoleCommandEvent $event) use (&$extraValue) { + $definition = $event->getCommand()->getDefinition(); + $input = $event->getInput(); + + $definition->addOption(new InputOption('extra', null, InputOption::VALUE_REQUIRED)); + $input->bind($definition); + + $extraValue = $input->getOption('extra'); + }); + + $application = new Application(); + $application->setDispatcher($dispatcher); + $application->setAutoExit(false); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo', '--extra' => 'some test value')); + + $this->assertEquals('some test value', $extraValue); + } + + /** + * @group legacy + */ + public function testTerminalDimensions() + { + $application = new Application(); + $originalDimensions = $application->getTerminalDimensions(); + $this->assertCount(2, $originalDimensions); + + $width = 80; + if ($originalDimensions[0] == $width) { + $width = 100; + } + + $application->setTerminalDimensions($width, 80); + $this->assertSame(array($width, 80), $application->getTerminalDimensions()); + } + + public function testSetRunCustomDefaultCommand() + { + $command = new \FooCommand(); + + $application = new Application(); + $application->setAutoExit(false); + $application->add($command); + $application->setDefaultCommand($command->getName()); + + $tester = new ApplicationTester($application); + $tester->run(array()); + $this->assertEquals('interact called'.PHP_EOL.'called'.PHP_EOL, $tester->getDisplay(), 'Application runs the default set command if different from \'list\' command'); + + $application = new CustomDefaultCommandApplication(); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + $tester->run(array()); + + $this->assertEquals('interact called'.PHP_EOL.'called'.PHP_EOL, $tester->getDisplay(), 'Application runs the default set command if different from \'list\' command'); + } + + public function testSetRunCustomSingleCommand() + { + $command = new \FooCommand(); + + $application = new Application(); + $application->setAutoExit(false); + $application->add($command); + $application->setDefaultCommand($command->getName(), true); + + $tester = new ApplicationTester($application); + + $tester->run(array()); + $this->assertContains('called', $tester->getDisplay()); + + $tester->run(array('--help' => true)); + $this->assertContains('The foo:bar command', $tester->getDisplay()); + } + + /** + * @requires function posix_isatty + */ + public function testCanCheckIfTerminalIsInteractive() + { + $application = new CustomDefaultCommandApplication(); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'help')); + + $this->assertFalse($tester->getInput()->hasParameterOption(array('--no-interaction', '-n'))); + + $inputStream = $tester->getInput()->getStream(); + $this->assertEquals($tester->getInput()->isInteractive(), @posix_isatty($inputStream)); + } + + protected function getDispatcher($skipCommand = false) + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener('console.command', function (ConsoleCommandEvent $event) use ($skipCommand) { + $event->getOutput()->write('before.'); + + if ($skipCommand) { + $event->disableCommand(); + } + }); + $dispatcher->addListener('console.terminate', function (ConsoleTerminateEvent $event) use ($skipCommand) { + $event->getOutput()->writeln('after.'); + + if (!$skipCommand) { + $event->setExitCode(ConsoleCommandEvent::RETURN_CODE_DISABLED); + } + }); + $dispatcher->addListener('console.error', function (ConsoleErrorEvent $event) { + $event->getOutput()->write('error.'); + + $event->setError(new \LogicException('error.', $event->getExitCode(), $event->getError())); + }); + + return $dispatcher; + } + + /** + * @requires PHP 7 + */ + public function testErrorIsRethrownIfNotHandledByConsoleErrorEventWithCatchingEnabled() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setDispatcher(new EventDispatcher()); + + $application->register('dym')->setCode(function (InputInterface $input, OutputInterface $output) { + new \UnknownClass(); + }); + + $tester = new ApplicationTester($application); + + try { + $tester->run(array('command' => 'dym')); + $this->fail('->run() should rethrow PHP errors if not handled via ConsoleErrorEvent.'); + } catch (\Error $e) { + $this->assertSame($e->getMessage(), 'Class \'UnknownClass\' not found'); + } + } +} + +class CustomApplication extends Application +{ + /** + * Overwrites the default input definition. + * + * @return InputDefinition An InputDefinition instance + */ + protected function getDefaultInputDefinition() + { + return new InputDefinition(array(new InputOption('--custom', '-c', InputOption::VALUE_NONE, 'Set the custom input definition.'))); + } + + /** + * Gets the default helper set with the helpers that should always be available. + * + * @return HelperSet A HelperSet instance + */ + protected function getDefaultHelperSet() + { + return new HelperSet(array(new FormatterHelper())); + } +} + +class CustomDefaultCommandApplication extends Application +{ + /** + * Overwrites the constructor in order to set a different default command. + */ + public function __construct() + { + parent::__construct(); + + $command = new \FooCommand(); + $this->add($command); + $this->setDefaultCommand($command->getName()); + } +} diff --git a/vendor/symfony/console/Tests/Command/CommandTest.php b/vendor/symfony/console/Tests/Command/CommandTest.php new file mode 100644 index 00000000..93b4721c --- /dev/null +++ b/vendor/symfony/console/Tests/Command/CommandTest.php @@ -0,0 +1,434 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\Console\Tester\CommandTester; + +class CommandTest extends TestCase +{ + protected static $fixturesPath; + + public static function setUpBeforeClass() + { + self::$fixturesPath = __DIR__.'/../Fixtures/'; + require_once self::$fixturesPath.'/TestCommand.php'; + } + + public function testConstructor() + { + $command = new Command('foo:bar'); + $this->assertEquals('foo:bar', $command->getName(), '__construct() takes the command name as its first argument'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The command defined in "Symfony\Component\Console\Command\Command" cannot have an empty name. + */ + public function testCommandNameCannotBeEmpty() + { + new Command(); + } + + public function testSetApplication() + { + $application = new Application(); + $command = new \TestCommand(); + $command->setApplication($application); + $this->assertEquals($application, $command->getApplication(), '->setApplication() sets the current application'); + $this->assertEquals($application->getHelperSet(), $command->getHelperSet()); + } + + public function testSetApplicationNull() + { + $command = new \TestCommand(); + $command->setApplication(null); + $this->assertNull($command->getHelperSet()); + } + + public function testSetGetDefinition() + { + $command = new \TestCommand(); + $ret = $command->setDefinition($definition = new InputDefinition()); + $this->assertEquals($command, $ret, '->setDefinition() implements a fluent interface'); + $this->assertEquals($definition, $command->getDefinition(), '->setDefinition() sets the current InputDefinition instance'); + $command->setDefinition(array(new InputArgument('foo'), new InputOption('bar'))); + $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->setDefinition() also takes an array of InputArguments and InputOptions as an argument'); + $this->assertTrue($command->getDefinition()->hasOption('bar'), '->setDefinition() also takes an array of InputArguments and InputOptions as an argument'); + $command->setDefinition(new InputDefinition()); + } + + public function testAddArgument() + { + $command = new \TestCommand(); + $ret = $command->addArgument('foo'); + $this->assertEquals($command, $ret, '->addArgument() implements a fluent interface'); + $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->addArgument() adds an argument to the command'); + } + + public function testAddOption() + { + $command = new \TestCommand(); + $ret = $command->addOption('foo'); + $this->assertEquals($command, $ret, '->addOption() implements a fluent interface'); + $this->assertTrue($command->getDefinition()->hasOption('foo'), '->addOption() adds an option to the command'); + } + + public function testSetHidden() + { + $command = new \TestCommand(); + $command->setHidden(true); + $this->assertTrue($command->isHidden()); + } + + public function testGetNamespaceGetNameSetName() + { + $command = new \TestCommand(); + $this->assertEquals('namespace:name', $command->getName(), '->getName() returns the command name'); + $command->setName('foo'); + $this->assertEquals('foo', $command->getName(), '->setName() sets the command name'); + + $ret = $command->setName('foobar:bar'); + $this->assertEquals($command, $ret, '->setName() implements a fluent interface'); + $this->assertEquals('foobar:bar', $command->getName(), '->setName() sets the command name'); + } + + /** + * @dataProvider provideInvalidCommandNames + */ + public function testInvalidCommandNames($name) + { + if (method_exists($this, 'expectException')) { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage(sprintf('Command name "%s" is invalid.', $name)); + } else { + $this->setExpectedException('InvalidArgumentException', sprintf('Command name "%s" is invalid.', $name)); + } + + $command = new \TestCommand(); + $command->setName($name); + } + + public function provideInvalidCommandNames() + { + return array( + array(''), + array('foo:'), + ); + } + + public function testGetSetDescription() + { + $command = new \TestCommand(); + $this->assertEquals('description', $command->getDescription(), '->getDescription() returns the description'); + $ret = $command->setDescription('description1'); + $this->assertEquals($command, $ret, '->setDescription() implements a fluent interface'); + $this->assertEquals('description1', $command->getDescription(), '->setDescription() sets the description'); + } + + public function testGetSetHelp() + { + $command = new \TestCommand(); + $this->assertEquals('help', $command->getHelp(), '->getHelp() returns the help'); + $ret = $command->setHelp('help1'); + $this->assertEquals($command, $ret, '->setHelp() implements a fluent interface'); + $this->assertEquals('help1', $command->getHelp(), '->setHelp() sets the help'); + $command->setHelp(''); + $this->assertEquals('', $command->getHelp(), '->getHelp() does not fall back to the description'); + } + + public function testGetProcessedHelp() + { + $command = new \TestCommand(); + $command->setHelp('The %command.name% command does... Example: php %command.full_name%.'); + $this->assertContains('The namespace:name command does...', $command->getProcessedHelp(), '->getProcessedHelp() replaces %command.name% correctly'); + $this->assertNotContains('%command.full_name%', $command->getProcessedHelp(), '->getProcessedHelp() replaces %command.full_name%'); + + $command = new \TestCommand(); + $command->setHelp(''); + $this->assertContains('description', $command->getProcessedHelp(), '->getProcessedHelp() falls back to the description'); + } + + public function testGetSetAliases() + { + $command = new \TestCommand(); + $this->assertEquals(array('name'), $command->getAliases(), '->getAliases() returns the aliases'); + $ret = $command->setAliases(array('name1')); + $this->assertEquals($command, $ret, '->setAliases() implements a fluent interface'); + $this->assertEquals(array('name1'), $command->getAliases(), '->setAliases() sets the aliases'); + } + + public function testSetAliasesNull() + { + $command = new \TestCommand(); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException'); + $command->setAliases(null); + } + + public function testGetSynopsis() + { + $command = new \TestCommand(); + $command->addOption('foo'); + $command->addArgument('bar'); + $this->assertEquals('namespace:name [--foo] [--] []', $command->getSynopsis(), '->getSynopsis() returns the synopsis'); + } + + public function testAddGetUsages() + { + $command = new \TestCommand(); + $command->addUsage('foo1'); + $command->addUsage('foo2'); + $this->assertContains('namespace:name foo1', $command->getUsages()); + $this->assertContains('namespace:name foo2', $command->getUsages()); + } + + public function testGetHelper() + { + $application = new Application(); + $command = new \TestCommand(); + $command->setApplication($application); + $formatterHelper = new FormatterHelper(); + $this->assertEquals($formatterHelper->getName(), $command->getHelper('formatter')->getName(), '->getHelper() returns the correct helper'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Cannot retrieve helper "formatter" because there is no HelperSet defined. + */ + public function testGetHelperWithoutHelperSet() + { + $command = new \TestCommand(); + $command->getHelper('formatter'); + } + + public function testMergeApplicationDefinition() + { + $application1 = new Application(); + $application1->getDefinition()->addArguments(array(new InputArgument('foo'))); + $application1->getDefinition()->addOptions(array(new InputOption('bar'))); + $command = new \TestCommand(); + $command->setApplication($application1); + $command->setDefinition($definition = new InputDefinition(array(new InputArgument('bar'), new InputOption('foo')))); + + $r = new \ReflectionObject($command); + $m = $r->getMethod('mergeApplicationDefinition'); + $m->setAccessible(true); + $m->invoke($command); + $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition() merges the application arguments and the command arguments'); + $this->assertTrue($command->getDefinition()->hasArgument('bar'), '->mergeApplicationDefinition() merges the application arguments and the command arguments'); + $this->assertTrue($command->getDefinition()->hasOption('foo'), '->mergeApplicationDefinition() merges the application options and the command options'); + $this->assertTrue($command->getDefinition()->hasOption('bar'), '->mergeApplicationDefinition() merges the application options and the command options'); + + $m->invoke($command); + $this->assertEquals(3, $command->getDefinition()->getArgumentCount(), '->mergeApplicationDefinition() does not try to merge twice the application arguments and options'); + } + + public function testMergeApplicationDefinitionWithoutArgsThenWithArgsAddsArgs() + { + $application1 = new Application(); + $application1->getDefinition()->addArguments(array(new InputArgument('foo'))); + $application1->getDefinition()->addOptions(array(new InputOption('bar'))); + $command = new \TestCommand(); + $command->setApplication($application1); + $command->setDefinition($definition = new InputDefinition(array())); + + $r = new \ReflectionObject($command); + $m = $r->getMethod('mergeApplicationDefinition'); + $m->setAccessible(true); + $m->invoke($command, false); + $this->assertTrue($command->getDefinition()->hasOption('bar'), '->mergeApplicationDefinition(false) merges the application and the command options'); + $this->assertFalse($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition(false) does not merge the application arguments'); + + $m->invoke($command, true); + $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition(true) merges the application arguments and the command arguments'); + + $m->invoke($command); + $this->assertEquals(2, $command->getDefinition()->getArgumentCount(), '->mergeApplicationDefinition() does not try to merge twice the application arguments'); + } + + public function testRunInteractive() + { + $tester = new CommandTester(new \TestCommand()); + + $tester->execute(array(), array('interactive' => true)); + + $this->assertEquals('interact called'.PHP_EOL.'execute called'.PHP_EOL, $tester->getDisplay(), '->run() calls the interact() method if the input is interactive'); + } + + public function testRunNonInteractive() + { + $tester = new CommandTester(new \TestCommand()); + + $tester->execute(array(), array('interactive' => false)); + + $this->assertEquals('execute called'.PHP_EOL, $tester->getDisplay(), '->run() does not call the interact() method if the input is not interactive'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage You must override the execute() method in the concrete command class. + */ + public function testExecuteMethodNeedsToBeOverridden() + { + $command = new Command('foo'); + $command->run(new StringInput(''), new NullOutput()); + } + + /** + * @expectedException \Symfony\Component\Console\Exception\InvalidOptionException + * @expectedExceptionMessage The "--bar" option does not exist. + */ + public function testRunWithInvalidOption() + { + $command = new \TestCommand(); + $tester = new CommandTester($command); + $tester->execute(array('--bar' => true)); + } + + public function testRunReturnsIntegerExitCode() + { + $command = new \TestCommand(); + $exitCode = $command->run(new StringInput(''), new NullOutput()); + $this->assertSame(0, $exitCode, '->run() returns integer exit code (treats null as 0)'); + + $command = $this->getMockBuilder('TestCommand')->setMethods(array('execute'))->getMock(); + $command->expects($this->once()) + ->method('execute') + ->will($this->returnValue('2.3')); + $exitCode = $command->run(new StringInput(''), new NullOutput()); + $this->assertSame(2, $exitCode, '->run() returns integer exit code (casts numeric to int)'); + } + + public function testRunWithApplication() + { + $command = new \TestCommand(); + $command->setApplication(new Application()); + $exitCode = $command->run(new StringInput(''), new NullOutput()); + + $this->assertSame(0, $exitCode, '->run() returns an integer exit code'); + } + + public function testRunReturnsAlwaysInteger() + { + $command = new \TestCommand(); + + $this->assertSame(0, $command->run(new StringInput(''), new NullOutput())); + } + + public function testRunWithProcessTitle() + { + $command = new \TestCommand(); + $command->setApplication(new Application()); + $command->setProcessTitle('foo'); + $this->assertSame(0, $command->run(new StringInput(''), new NullOutput())); + if (function_exists('cli_set_process_title')) { + if (null === @cli_get_process_title() && 'Darwin' === PHP_OS) { + $this->markTestSkipped('Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.'); + } + $this->assertEquals('foo', cli_get_process_title()); + } + } + + public function testSetCode() + { + $command = new \TestCommand(); + $ret = $command->setCode(function (InputInterface $input, OutputInterface $output) { + $output->writeln('from the code...'); + }); + $this->assertEquals($command, $ret, '->setCode() implements a fluent interface'); + $tester = new CommandTester($command); + $tester->execute(array()); + $this->assertEquals('interact called'.PHP_EOL.'from the code...'.PHP_EOL, $tester->getDisplay()); + } + + public function getSetCodeBindToClosureTests() + { + return array( + array(true, 'not bound to the command'), + array(false, 'bound to the command'), + ); + } + + /** + * @dataProvider getSetCodeBindToClosureTests + */ + public function testSetCodeBindToClosure($previouslyBound, $expected) + { + $code = createClosure(); + if ($previouslyBound) { + $code = $code->bindTo($this); + } + + $command = new \TestCommand(); + $command->setCode($code); + $tester = new CommandTester($command); + $tester->execute(array()); + $this->assertEquals('interact called'.PHP_EOL.$expected.PHP_EOL, $tester->getDisplay()); + } + + public function testSetCodeWithStaticClosure() + { + $command = new \TestCommand(); + $command->setCode(self::createClosure()); + $tester = new CommandTester($command); + $tester->execute(array()); + + if (\PHP_VERSION_ID < 70000) { + // Cannot bind static closures in PHP 5 + $this->assertEquals('interact called'.PHP_EOL.'not bound'.PHP_EOL, $tester->getDisplay()); + } else { + // Can bind static closures in PHP 7 + $this->assertEquals('interact called'.PHP_EOL.'bound'.PHP_EOL, $tester->getDisplay()); + } + } + + private static function createClosure() + { + return function (InputInterface $input, OutputInterface $output) { + $output->writeln(isset($this) ? 'bound' : 'not bound'); + }; + } + + public function testSetCodeWithNonClosureCallable() + { + $command = new \TestCommand(); + $ret = $command->setCode(array($this, 'callableMethodCommand')); + $this->assertEquals($command, $ret, '->setCode() implements a fluent interface'); + $tester = new CommandTester($command); + $tester->execute(array()); + $this->assertEquals('interact called'.PHP_EOL.'from the code...'.PHP_EOL, $tester->getDisplay()); + } + + public function callableMethodCommand(InputInterface $input, OutputInterface $output) + { + $output->writeln('from the code...'); + } +} + +// In order to get an unbound closure, we should create it outside a class +// scope. +function createClosure() +{ + return function (InputInterface $input, OutputInterface $output) { + $output->writeln($this instanceof Command ? 'bound to the command' : 'not bound to the command'); + }; +} diff --git a/vendor/symfony/console/Tests/Command/HelpCommandTest.php b/vendor/symfony/console/Tests/Command/HelpCommandTest.php new file mode 100644 index 00000000..4d618ac1 --- /dev/null +++ b/vendor/symfony/console/Tests/Command/HelpCommandTest.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\Console\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Console\Command\HelpCommand; +use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\Application; + +class HelpCommandTest extends TestCase +{ + public function testExecuteForCommandAlias() + { + $command = new HelpCommand(); + $command->setApplication(new Application()); + $commandTester = new CommandTester($command); + $commandTester->execute(array('command_name' => 'li'), array('decorated' => false)); + $this->assertContains('list [options] [--] []', $commandTester->getDisplay(), '->execute() returns a text help for the given command alias'); + $this->assertContains('format=FORMAT', $commandTester->getDisplay(), '->execute() returns a text help for the given command alias'); + $this->assertContains('raw', $commandTester->getDisplay(), '->execute() returns a text help for the given command alias'); + } + + public function testExecuteForCommand() + { + $command = new HelpCommand(); + $commandTester = new CommandTester($command); + $command->setCommand(new ListCommand()); + $commandTester->execute(array(), array('decorated' => false)); + $this->assertContains('list [options] [--] []', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertContains('format=FORMAT', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertContains('raw', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + } + + public function testExecuteForCommandWithXmlOption() + { + $command = new HelpCommand(); + $commandTester = new CommandTester($command); + $command->setCommand(new ListCommand()); + $commandTester->execute(array('--format' => 'xml')); + $this->assertContains('getDisplay(), '->execute() returns an XML help text if --xml is passed'); + } + + public function testExecuteForApplicationCommand() + { + $application = new Application(); + $commandTester = new CommandTester($application->get('help')); + $commandTester->execute(array('command_name' => 'list')); + $this->assertContains('list [options] [--] []', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertContains('format=FORMAT', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertContains('raw', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + } + + public function testExecuteForApplicationCommandWithXmlOption() + { + $application = new Application(); + $commandTester = new CommandTester($application->get('help')); + $commandTester->execute(array('command_name' => 'list', '--format' => 'xml')); + $this->assertContains('list [--raw] [--format FORMAT] [--] [<namespace>]', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertContains('getDisplay(), '->execute() returns an XML help text if --format=xml is passed'); + } +} diff --git a/vendor/symfony/console/Tests/Command/ListCommandTest.php b/vendor/symfony/console/Tests/Command/ListCommandTest.php new file mode 100644 index 00000000..64478ecc --- /dev/null +++ b/vendor/symfony/console/Tests/Command/ListCommandTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Console\Application; + +class ListCommandTest extends TestCase +{ + public function testExecuteListsCommands() + { + $application = new Application(); + $commandTester = new CommandTester($command = $application->get('list')); + $commandTester->execute(array('command' => $command->getName()), array('decorated' => false)); + + $this->assertRegExp('/help\s{2,}Displays help for a command/', $commandTester->getDisplay(), '->execute() returns a list of available commands'); + } + + public function testExecuteListsCommandsWithXmlOption() + { + $application = new Application(); + $commandTester = new CommandTester($command = $application->get('list')); + $commandTester->execute(array('command' => $command->getName(), '--format' => 'xml')); + $this->assertRegExp('/